Getting method parameter names in Python

2019-01-01 05:15发布

问题:

Given the Python function:

def aMethod(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 aMethod(\"a\", \"b\")?

回答1:

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

>>> inspect.getargspec(aMethod)
([\'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.getargspec(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 obtain ValueError in the case you use inspect.getargspec() with a built-in function.

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

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


回答2:

In CPython, the number of arguments is

aMethod.func_code.co_argcount

and their names are in the beginning of

aMethod.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.



回答3:

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]


回答4:

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.


回答5:

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}


回答6:

Python 3.5+:

DeprecationWarning: inspect.getargspec() is deprecated, use inspect.signature() instead

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


回答7:

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.



回答8:

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)


回答9:

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={})


回答10:

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}


回答11:

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


回答12:

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.