How to get method parameter names?
PythonDecoratorIntrospectionPython DatamodelPython 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.