As stated by user2357112 in the comments, you can use inspect.signature.bind to obtain a dictionary of the arguments and keyword arguments.

Your decorator could then be something like:

class my_decorator:
    def __init__(self, option=None):
        self.option = option

    def __call__(self, fn):
        @functools.wraps(fn)
        def decorated(*args, **kwargs):
            fn_args = inspect.signature(fn).bind(*args, **kwargs)
            
            # Then if you really needed to make them as variables 
            # rather than a dict for a reason I can't imagine
            locals.update(fn_args)

            return fn(*args, **kwargs)

        return decorated

If like me you need to keep maintaining python 2.7 and python 3 compatibility, there is a similar function inspect.getcallargs which works for both, even though it's deprecated post 3.5

Answer from Erwan Leroy on Stack Overflow
🌐
Python
docs.python.org › 3 › library › inspect.html
inspect — Inspect live objects
For bound methods, bind also the first argument (typically named self) to the associated instance. A dict is returned, mapping the argument names (including the names of the * and ** arguments, if any) to their values from args and kwds. In case of invoking func incorrectly, i.e. whenever func(*args, **kwds) would raise an exception because of incompatible signature, an exception of the same type and the same or similar message is raised.
🌐
GitHub
gist.github.com › encukou › 5060767
inspect.Signature.bind · GitHub
inspect.Signature.bind. GitHub Gist: instantly share code, notes, and snippets.
🌐
Python Module of the Week
pymotw.com › 3 › inspect
Inspect Live Objects — PyMOTW 3
import inspect import example sig = inspect.signature(example.module_level_function) partial = sig.bind_partial( 'this is arg1', ) print('Without defaults:') for name, value in partial.arguments.items(): print('{} = {!r}'.format(name, value)) print('\nWith defaults:') partial.apply_defaults() for name, value in partial.arguments.items(): print('{} = {!r}'.format(name, value)) apply_defaults() will add any values from the parameter defaults. $ python3 inspect_signature_bind_partial.py Without defaults: arg1 = 'this is arg1' With defaults: arg1 = 'this is arg1' arg2 = 'default' args = () kwargs = {}
🌐
Chipx86
chipx86.blog › 2025 › 07 › 12 › a-crash-course-on-python-function-signatures-and-typing
A crash course on Python function signatures and typing
August 8, 2025 - By the way, @staticmethod is a way of telling Python to never make an unbound method into a bound method when instantiating the object (it stays a function), and @classmethod is a way of telling Python to bind it immediately to the class it’s defined on (and not rebind when instantiating an object from the class). If you have a function, and you don’t know if it’s a standard function, a classmethod, a bound method, or an unbound method, how can you tell? Bound methods have a __self__ attribute pointing to the parent object (and inspect.ismethod() will be True).
Top answer
1 of 2
2

As stated by user2357112 in the comments, you can use inspect.signature.bind to obtain a dictionary of the arguments and keyword arguments.

Your decorator could then be something like:

class my_decorator:
    def __init__(self, option=None):
        self.option = option

    def __call__(self, fn):
        @functools.wraps(fn)
        def decorated(*args, **kwargs):
            fn_args = inspect.signature(fn).bind(*args, **kwargs)
            
            # Then if you really needed to make them as variables 
            # rather than a dict for a reason I can't imagine
            locals.update(fn_args)

            return fn(*args, **kwargs)

        return decorated

If like me you need to keep maintaining python 2.7 and python 3 compatibility, there is a similar function inspect.getcallargs which works for both, even though it's deprecated post 3.5

2 of 2
1

Well, considerable effort researching existing solutions turned nothing up, so I have worked up a solution which works, though I'm not 100% happy with (as I'm using 'exec' which is optional really, the main trick inspect.signature.parameters that helps us interpret incoming arguments in the context of the decorated function's expectations):

from inspect import signature

... then inside 'decorated' ...

allargs = signature(fn).parameters

# allargs is an OrderedDict, the keys of which are the arguments of fn.
# We build therfrom, a list of arguments we are seeking in args and kwargs
seeking = list(allargs.keys())
found = {}

# Methods by convention have the first argument "self". Even if it's
# not a method, setting a local variable 'self' is fraught with issues
# And so we need to remap it. Also if we've explicitly decorated a method
# we remap the first argument. We call it 'selfie' interanally, but in
# provided key_patterns, accept 'self' as a reference.
if seeking[0] == 'self' or is_method:
    selfie = args[0]
    found['selfie'] = selfie
    seeking.pop(0)
    sarg = 1
    is_method = True
else:
    sarg = 0

# For classifying arguments see:
# https://docs.python.org/3/library/inspect.html#inspect.Parameter
#
# We start by consuming all the args.
if seeking:
    for arg in args[sarg:]:
        # This should never happen, but if someone calls the decorated function
        # with more args than the original function can accept that's clearly
        # an erroneous call.
        if len(seeking) == 0:
            raise TooManyArgs(f"Decorated function has been called with {len(args)} positional arguments when only {len(allargs)} args are accepted by the decorated function. ")

        # Set a local variable, feigning the conditions that fn would see
        # if seeking[0] is 'self' this exhibits odd dysfunctional behaviour
        # and so above we mapped 'self' to 'selfie' internall of this decorator.
        found[seeking[0]] = arg
        exec(f"{seeking[0]} = arg")
        seeking.pop(0)

    # If we did not find all that we seek by consuming args, consume kwargs
    if seeking:
        for kwarg, val in kwargs.items():
            if kwarg in seeking:
                # Should never happen, but if someone calls the decorated function
                # with more args than the original function can accept that's clearly
                # an erroneous call.
                if len(seeking) == 0:
                    raise TooManyKwargs(f"Decorated function has been called with {len(kwargs)} keyword arguments after {len(args)} positional arguments when only {len(allargs)} args are accepted by the decorated function. ")

                arg = seeking.index(kwarg)
                found[seeking[arg]] = val
                exec(f"{seeking[arg]} = val")
                seeking.pop(arg)

        if seeking:
            # Any that remain we can check for default values
            for arg in seeking:
                props = allargs[arg]
                if props.default != props.empty:
                    pos = seeking.index(arg)
                    found[seeking[pos]] = props.default
                    exec(f"{seeking[pos]} = props.default")
                    seeking.pop(pos)

            # If any remain, then clearly not all the argument fn needs have been supplied
            # to its decorated version.
            if seeking:
                raise TooFewArgs(f"Decorated function expects arguments ({', '.join(seeking)}), which it was not called with.")

That calls on a few custom exceptions:

class TooManyArgs(Exception):
    pass

class TooManyKwargs(Exception):
    pass

class TooFewArgs(Exception):
    pass

but works fine. The upshot is local variables in decorated which are just as they would be seen inside the decorated function.

While a spot need, it is utilised here:

It is used here:

https://pypi.org/project/django-cache-memoized/

and in action here:

https://github.com/bernd-wechner/django-cache-memoized/blob/master/src/django_cache_memoized/__init__.py

(We'll hope the specious vote to close a very sensible, well researched question doesn't find 2 supporters. It's a very interesting question IMHO and seems doable, though a canonical approach would be preferred.)

🌐
IPython
ipython.org › ipython-doc › 3 › api › generated › IPython.utils.signatures.html
Module: utils.signatures — IPython 3.2.1 documentation
Back port of Python 3.3’s function signature tools from the inspect module, modified to be compatible with Python 2.7 and 3.2+.
🌐
Martin Heinz
martinheinz.dev › blog › 82
All The Ways To Introspect Python Objects at Runtime | Martin Heinz | Personal Website & Blog
October 3, 2022 - If the parameters don't satisfy the signature, we receive an exception. If all is good, we can proceed to access the bound parameter using the return value of bind method. The above worked for validating parameters inside a basic function, but what if we wanted to validate that a generator gets all the necessary parameters from a decorated function? import functools import inspect def login_required(fn): @functools.wraps(fn) def wrapper(*args, **kwargs): func_args = inspect.Signature.from_callable(fn).parameters if "username" not in func_args: raise Exception("Missing username argument") # ...
Find elsewhere
🌐
ProgramCreek
programcreek.com › python › example › 81294 › inspect.signature
Python Examples of inspect.signature
def generate_lock(self, task_name, task_args=None, task_kwargs=None): unique_on = self.unique_on task_args = task_args or [] task_kwargs = task_kwargs or {} if unique_on: if isinstance(unique_on, str): unique_on = [unique_on] sig = inspect.signature(self.run) bound = sig.bind(*task_args, **task_kwargs).arguments unique_args = [] unique_kwargs = {key: bound[key] for key in unique_on} else: unique_args = task_args unique_kwargs = task_kwargs return util.generate_lock( task_name, unique_args, unique_kwargs, key_prefix=self.singleton_config.key_prefix, )
🌐
Real Python
realpython.com › ref › stdlib › inspect
inspect | Python Standard Library – Real Python
>>> def example_function(a, b=2, *args, **kwargs): ... pass ... >>> inspect.signature(example_function) <Signature (a, b=2, *args, **kwargs)>
🌐
Readthedocs
python-forge.readthedocs.io › en › latest › signature.html
Signatures, parameters and return types — forge 18.6.0 documentation
It’s primary responsibility is to apply a series of transforms and validations on an value and map that value to the parameter of an underlying callable. It mimics the API of inspect.Parameter, and extends it further to provide enriched functionality for value transformation. The kind of a parameter determines it’s position in a signature and how a user can provide its argument value.
🌐
Basicexamples
basicexamples.com › example › python › inspect-signature-bind-partial
Basic example of inspect.Signature.bind_partial() in Python
Simple usage example of `inspect.Signature.bind_partial()`. `inspect.Signature.bind_partial()` is a Python function from the `inspect` module that allows you to partially bind arguments to a function's signature. It returns a new function signature with some arguments already populated.
🌐
Runebook.dev
runebook.dev › en › docs › python › library › inspect › inspect.BoundArguments.signature
The Power of Introspection: Python's BoundArguments.signature Explained
October 22, 2025 - The inspect module in Python provides ....bind(*args, kwargs) on a function's signature, it takes the arguments you provided and binds them to the parameters defined in the signature....
🌐
UCI
ics.uci.edu › ~pattis › ICS-33 › lectures › introspection.txt
Introspection (Function Dispatching and Stack Crawling)
Assuming ps = inspect.signature(f).parameters what can we do with ps, assuming the the function f has a parameter named x? (1) ps['x'].name 'x' (2) ps['x'].default default value or inspect._empty if no default value (3) ps['x'].annotation annotation or inspect._empty if no annotation (4) ps['x'].kind either POSITIONAL_ONLY if it appears in signature BEFORE special / POSITIONAL_OR_KEYWORD standard Python parameter VAR_POSITIONAL if it appears in signature as *name KEYWORD_ONLY if it appears in signature AFTER * or *name VAR_KEYWORD if appears in signature as **name With this information about a function, Python can determine the bindings of arguments to parameters, given any function call (we discussed these powerful rules in the first week's lecture).
🌐
GitHub
github.com › GrahamDumpleton › wrapt › issues › 190
Using inspect module to detect signature and reorganize the function arguments · Issue #190 · GrahamDumpleton/wrapt
October 5, 2021 - import inspect from functools import wraps def my_decorator(func): sig = inspect.signature(func) @wraps(func) def wrapped(*args, **kwargs): # re-order args and kwargs into same order as function signature bound = sig.bind(*args, **kwargs) # do calculation using correct order return do_the_calc(*bound.arguments.values()) return wrapped ·
Author   Ricyteach
🌐
Fluentpython
fluentpython.com › extra › function-introspection
Introspection of Function Parameters | Fluent Python, the lizard book
Besides name, default, and kind, ... in [type_hints_in_def_ch]. An inspect.Signature object has a bind method that takes any number of arguments and binds them to the parameters in the signature, applying the usual rules for matching actual arguments to formal paramet...
🌐
Runebook.dev
runebook.dev › en › docs › python › library › inspect › inspect.Signature.bind_partial
Argument Handling Gotchas: Common Issues with Python's bind_partial() and Better Options
Get the signature sig = inspect.signature(greet) # 2. Use bind_partial() - we only supply the positional argument 'name' # This works because 'formality' has a default and 'enthusiasm' is keyword-only.
🌐
LabEx
labex.io › tutorials › python-how-to-bind-function-signatures-safely-419808
Python - How to bind function signatures safely
In Python, understanding function signatures is crucial for creating flexible and robust code. A function signature consists of several key components: ... import inspect def example_function(a: int, b: str = "default"): pass ## Inspect signature details signature = inspect.signature(example_function) print(signature.parameters)