How to get method parameter names?

PythonDecoratorIntrospectionPython Datamodel

Python Problem Overview


Given the Python function:

def a_method(arg1, arg2):
    pass

How can I extract the number and names of the arguments. I.e., given that I have a reference to func, I want the func.[something] to return ("arg1", "arg2").

The usage scenario for this is that I have a decorator, and I wish to use the method arguments in the same order that they appear for the actual function as a key. I.e., how would the decorator look that printed "a,b" when I call a_method("a", "b")?

Python Solutions


Solution 1 - Python

Take a look at the inspect module - this will do the inspection of the various code object properties for you.

>>> inspect.getfullargspec(a_method)
(['arg1', 'arg2'], None, None, None)

The other results are the name of the *args and **kwargs variables, and the defaults provided. ie.

>>> def foo(a, b, c=4, *arglist, **keywords): pass
>>> inspect.getfullargspec(foo)
(['a', 'b', 'c'], 'arglist', 'keywords', (4,))

Note that some callables may not be introspectable in certain implementations of Python. For Example, in CPython, some built-in functions defined in C provide no metadata about their arguments. As a result, you will get a ValueError if you use inspect.getfullargspec() on a built-in function.

Since Python 3.3, you can use inspect.signature() to see the call signature of a callable object:

>>> inspect.signature(foo)
<Signature (a, b, c=4, *arglist, **keywords)>

Solution 2 - Python

In CPython, the number of arguments is

a_method.func_code.co_argcount

and their names are in the beginning of

a_method.func_code.co_varnames

These are implementation details of CPython, so this probably does not work in other implementations of Python, such as IronPython and Jython.

One portable way to admit "pass-through" arguments is to define your function with the signature func(*args, **kwargs). This is used a lot in e.g. matplotlib, where the outer API layer passes lots of keyword arguments to the lower-level API.

Solution 3 - Python

The Python 3 version is:

def _get_args_dict(fn, args, kwargs):
    args_names = fn.__code__.co_varnames[:fn.__code__.co_argcount]
    return {**dict(zip(args_names, args)), **kwargs}

The method returns a dictionary containing both args and kwargs.

Solution 4 - Python

In a decorator method, you can list arguments of the original method in this way:

import inspect, itertools 

def my_decorator():
    
        def decorator(f):
    
            def wrapper(*args, **kwargs):
    
                # if you want arguments names as a list:
                args_name = inspect.getargspec(f)[0]
                print(args_name)

                # if you want names and values as a dictionary:
                args_dict = dict(itertools.izip(args_name, args))
                print(args_dict)

                # if you want values as a list:
                args_values = args_dict.values()
                print(args_values)

If the **kwargs are important for you, then it will be a bit complicated:

        def wrapper(*args, **kwargs):

            args_name = list(OrderedDict.fromkeys(inspect.getargspec(f)[0] + kwargs.keys()))
            args_dict = OrderedDict(list(itertools.izip(args_name, args)) + list(kwargs.iteritems()))
            args_values = args_dict.values()

Example:

@my_decorator()
def my_function(x, y, z=3):
    pass


my_function(1, y=2, z=3, w=0)
# prints:
# ['x', 'y', 'z', 'w']
# {'y': 2, 'x': 1, 'z': 3, 'w': 0}
# [1, 2, 3, 0]

        

        
     

Solution 5 - Python

I think what you're looking for is the locals method -


In [6]: def test(a, b):print locals()
...:




In [7]: test(1,2)

{'a': 1, 'b': 2}

In [7]: test(1,2)
{'a': 1, 'b': 2}

Solution 6 - Python

Python 3.5+:

> DeprecationWarning: inspect.getargspec() is deprecated since Python 3.0, use inspect.signature() or inspect.getfullargspec()

So previously:

func_args = inspect.getargspec(function).args

Now:

func_args = list(inspect.signature(function).parameters.keys())

To test:

'arg' in list(inspect.signature(function).parameters.keys())

Given that we have function 'function' which takes argument 'arg', this will evaluate as True, otherwise as False.

Example from the Python console:

Python 3.6.0 (v3.6.0:41df79263a11, Dec 23 2016, 07:18:10) [MSC v.1900 32 bit (Intel)] on win32
>>> import inspect
>>> 'iterable' in list(inspect.signature(sum).parameters.keys())
True

Solution 7 - Python

Here is something I think will work for what you want, using a decorator.

class LogWrappedFunction(object):
    def __init__(self, function):
        self.function = function

    def logAndCall(self, *arguments, **namedArguments):
        print "Calling %s with arguments %s and named arguments %s" %\
                      (self.function.func_name, arguments, namedArguments)
        self.function.__call__(*arguments, **namedArguments)

def logwrap(function):
    return LogWrappedFunction(function).logAndCall

@logwrap
def doSomething(spam, eggs, foo, bar):
    print "Doing something totally awesome with %s and %s." % (spam, eggs)


doSomething("beans","rice", foo="wiggity", bar="wack")

Run it, it will yield the following output:

C:\scripts>python decoratorExample.py
Calling doSomething with arguments ('beans', 'rice') and named arguments {'foo':
 'wiggity', 'bar': 'wack'}
Doing something totally awesome with beans and rice.

Solution 8 - Python

In Python 3.+ with the Signature object at hand, an easy way to get a mapping between argument names to values, is using the Signature's bind() method!

For example, here is a decorator for printing a map like that:

import inspect

def decorator(f):
    def wrapper(*args, **kwargs):
        bound_args = inspect.signature(f).bind(*args, **kwargs)
        bound_args.apply_defaults()
        print(dict(bound_args.arguments))

        return f(*args, **kwargs)

    return wrapper

@decorator
def foo(x, y, param_with_default="bars", **kwargs):
    pass

foo(1, 2, extra="baz")
# This will print: {'kwargs': {'extra': 'baz'}, 'param_with_default': 'bars', 'y': 2, 'x': 1}

Solution 9 - Python

Here is another way to get the function parameters without using any module.

def get_parameters(func):
    keys = func.__code__.co_varnames[:func.__code__.co_argcount][::-1]
    sorter = {j: i for i, j in enumerate(keys[::-1])} 
    values = func.__defaults__[::-1]
    kwargs = {i: j for i, j in zip(keys, values)}
    sorted_args = tuple(
        sorted([i for i in keys if i not in kwargs], key=sorter.get)
    )
    sorted_kwargs = {
        i: kwargs[i] for i in sorted(kwargs.keys(), key=sorter.get)
    }   
    return sorted_args, sorted_kwargs


def f(a, b, c="hello", d="world"): var = a
    

print(get_parameters(f))

Output:

(('a', 'b'), {'c': 'hello', 'd': 'world'})

Solution 10 - Python

inspect.signature is very slow. Fastest way is

def f(a, b=1, *args, c, d=1, **kwargs):
   pass

f_code = f.__code__
f_code.co_varnames[:f_code.co_argcount + f_code.co_kwonlyargcount]  # ('a', 'b', 'c', 'd')

Solution 11 - Python

Update for Brian's answer:

If a function in Python 3 has keyword-only arguments, then you need to use inspect.getfullargspec:

def yay(a, b=10, *, c=20, d=30):
    pass
inspect.getfullargspec(yay)

yields this:

FullArgSpec(args=['a', 'b'], varargs=None, varkw=None, defaults=(10,), kwonlyargs=['c', 'd'], kwonlydefaults={'c': 20, 'd': 30}, annotations={})

Solution 12 - Python

Returns a list of argument names, takes care of partials and regular functions:

def get_func_args(f):
    if hasattr(f, 'args'):
        return f.args
    else:
        return list(inspect.signature(f).parameters)

Solution 13 - Python

In python 3, below is to make *args and **kwargs into a dict (use OrderedDict for python < 3.6 to maintain dict orders):

from functools import wraps

def display_param(func):
    @wraps(func)
    def wrapper(*args, **kwargs):

        param = inspect.signature(func).parameters
        all_param = {
            k: args[n] if n < len(args) else v.default
            for n, (k, v) in enumerate(param.items()) if k != 'kwargs'
        }
        all_param .update(kwargs)
        print(all_param)

        return func(**all_param)
    return wrapper

Solution 14 - Python

Simple easy to read answer as of python 3.0 onwards:

import inspect


args_names = inspect.signature(function).parameters.keys()
args_dict = {
    **dict(zip(args_names, args)),
    **kwargs,
}


Solution 15 - Python

To update a little bit Brian's answer, there is now a nice backport of inspect.signature that you can use in older python versions: funcsigs. So my personal preference would go for

try:  # python 3.3+
    from inspect import signature
except ImportError:
    from funcsigs import signature

def aMethod(arg1, arg2):
    pass

sig = signature(aMethod)
print(sig)

For fun, if you're interested in playing with Signature objects and even creating functions with random signatures dynamically you can have a look at my makefun project.

Solution 16 - Python

I was googling to find how to print function name and supplied arguments for an assignment I had to create a decorator to print them and I used this:

def print_func_name_and_args(func):
    
    def wrapper(*args, **kwargs):
    print(f"Function name: '{func.__name__}' supplied args: '{args}'")
    func(args[0], args[1], args[2])
    return wrapper


@print_func_name_and_args
def my_function(n1, n2, n3):
    print(n1 * n2 * n3)
    
my_function(1, 2, 3)

#Function name: 'my_function' supplied args: '(1, 2, 3)'

Solution 17 - Python

Is it possible to use inspect API to read constant argument value -1 from the lambda func fun in the code below?

def my_func(v, axis):
  pass

fun = lambda v: my_func(v, axis=-1)

Solution 18 - Python

What about dir() and vars() now?

Seems doing exactly what is being asked super simply…

Must be called from within the function scope.

But be wary that it will return all local variables so be sure to do it at the very beginning of the function if needed.

Also note that, as pointed out in the comments, this doesn't allow it to be done from outside the scope. So not exactly OP's scenario but still matches the question title. Hence my answer.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionStaaleView Question on Stackoverflow
Solution 1 - PythonBrianView Answer on Stackoverflow
Solution 2 - PythonJouni K. SeppänenView Answer on Stackoverflow
Solution 3 - PythonargaenView Answer on Stackoverflow
Solution 4 - PythonMehdi BehroozView Answer on Stackoverflow
Solution 5 - PythonDamianView Answer on Stackoverflow
Solution 6 - PythonPeter MajkoView Answer on Stackoverflow
Solution 7 - PythonhlzrView Answer on Stackoverflow
Solution 8 - PythonKfir EisnerView Answer on Stackoverflow
Solution 9 - PythondildeolupbitenView Answer on Stackoverflow
Solution 10 - PythonNikolay MakhalinView Answer on Stackoverflow
Solution 11 - PythonASMik09View Answer on Stackoverflow
Solution 12 - PythonloveshView Answer on Stackoverflow
Solution 13 - PythonAlphaView Answer on Stackoverflow
Solution 14 - PythonJose EnriqueView Answer on Stackoverflow
Solution 15 - PythonsmarieView Answer on Stackoverflow
Solution 16 - PythonBriscoView Answer on Stackoverflow
Solution 17 - Pythonx4444View Answer on Stackoverflow
Solution 18 - PythonjeromejView Answer on Stackoverflow