可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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.