Reputation: 8338
For this function
def eat_dog(name, should_digest=True):
print "ate dog named %s. Digested, too? %" % (name, str(should_digest))
I want to, external to the function, read its arguments and any default values attached. So for this specific example, I want to know that name
has no default value (i.e. that it is a required argument) and that True
is the default value for should_digest
.
I'm aware of inspect.getargspec()
, which does give me information about arguments and default values, but I see no connection between the two:
ArgSpec(args=['name', 'should_digest'], varargs=None, keywords=None, defaults=(True,))
From this output how can I tell that True
(in the defaults
tuple) is the default value for should_digest
?
Additionally, I'm aware of the "ask for forgiveness" model of approaching a problem, but unfortunately output from that error won't tell me the name of the missing argument:
>>> eat_dog()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: eat_dog() takes at least 1 argument (0 given)
To give context (why I want to do this), I'm exposing functions in a module over a JSON API. If the caller omits certain function arguments, I want to return a specific error that names the specific function argument that was omitted. If a client omits an argument, but there's a default provided in the function signature, I want to use that default.
Upvotes: 110
Views: 52421
Reputation: 1470
TLDR;
You can get this without any imports via some of the __dunder__
vars as mentioned by other posts. Putting that into a simple helper function can get you a dictionary of default values.
def get_defaults(fn):
"""
Get the default values of the passed function or method.
"""
output = {}
if fn.__defaults__ is not None:
# Get the names of all provided default values for args
default_varnames = list(fn.__code__.co_varnames)[:fn.__code__.co_argcount][-len(fn.__defaults__):]
# Update the output dictionary with the default values
output.update(dict(zip(default_varnames, fn.__defaults__)))
if fn.__kwdefaults__ is not None:
# Update the output dictionary with the keyword default values
output.update(fn.__kwdefaults__)
return output
Takes advantage of the following dunder vars:
.__code__.co_varnames
: A tuple of all input variable names.__code__.co_argcount
: The number of arguments (non keyword) in the function.__defaults__
: A tuple of the default values.__kwdefaults__
: A dict of the keyword default valuesEDIT 1 - Thanks to @griloHBG - Added if statement to prevent exceptions when no defaults are specified.
EDIT 2 - Modify to make more generic (supports both defaults and kwdefaults for functions and methods)
# Tests
def test_fn(a, b, c=3, d=4, *args, **kwargs):
pass
def test_fn_2(a, b, c=1, *args, d, e=2, **kwargs):
pass
print(get_defaults(test_fn)) #=> {'c': 3, 'd': 4}
print(get_defaults(test_fn_2)) #=> {'c': 1, 'e': 2}
# Class Tests
class Test:
def __init__(self, a, b, c=3, d=4, *args, **kwargs):
pass
def test_fn_2(self, a, b, c=1, *args, d, e=2, **kwargs):
pass
@staticmethod
def test_fn_3(a, b, c=1, *args, d, e=2, **kwargs):
pass
@classmethod
def test_fn_4(cls, a, b, c=1, *args, d, e=2, **kwargs):
pass
# Uninitialized Class Tests
print(get_defaults(Test.__init__)) #=> {'c': 3, 'd': 4}
print(get_defaults(Test.test_fn_2)) #=> {'c': 1, 'e': 2}
print(get_defaults(Test.test_fn_3)) #=> {'c': 1, 'e': 2}
print(get_defaults(Test.test_fn_4)) #=> {'c': 1, 'e': 2}
# Initialized Class Tests
test_object = Test(1, 2)
print(get_defaults(test_object.__init__)) #=> {'c': 3, 'd': 4}
print(get_defaults(test_object.test_fn_2)) #=> {'c': 1, 'e': 2}
print(get_defaults(Test.test_fn_3)) #=> {'c': 1, 'e': 2}
print(get_defaults(Test.test_fn_4)) #=> {'c': 1, 'e': 2}
Upvotes: 5
Reputation: 126
A simple modification of @conmak's answer to still return a dictionary with None
as values when the function has named arguments but no defaults:
def my_fn(a, b=2, c='a'):
pass
def get_defaults(fn):
### No arguments
if isinstance(fn.__code__.co_varnames, type(None)):
return {}
### no defaults
if isinstance(fn.__defaults__, type(None)):
return dict(zip(
fn.__code__.co_varnames,
[None]*(fn.__code__.co_argcount)
))
### every other case
return dict(zip(
fn.__code__.co_varnames,
[None]*(fn.__code__.co_argcount - len(fn.__defaults__)) + list(fn.__defaults__)
))
print(get_defaults(my_fn))
Should now give:
{'a': None, 'b': 2, 'c': 'a'}
Upvotes: 0
Reputation: 386
To those looking for a version to grab a specific default parameter with mgilson's answer.
value = signature(my_func).parameters['param_name'].default
Here's a full working version, done in Python 3.8.2
from inspect import signature
def my_func(a, b, c, param_name='apple'):
pass
value = signature(my_func).parameters['param_name'].default
print(value == 'apple') # True
Upvotes: 13
Reputation: 41168
Depending on exactly what you need, you might not need the inspect
module since you can check the __defaults__
attribute of the function:
>>> eat_dog.__defaults__
(True,)
>>> eat_dog.__code__.co_argcount
2
>>> eat_dog.__code__.co_varnames
('name', 'should_digest')
>>>
>>> eat_dog.__kwdefaults__
>>> eat_dog.__code__.co_kwonlyargcount
0
Upvotes: 13
Reputation: 415
In python, all the arguments with default value come after the arguments without default value. So the mapping should start from the end till you exhaust the default value list. Hence the logic:
dict(zip(reversed(args), reversed(defaults)))
gives the correctly mapped defaults.
Upvotes: 0
Reputation: 309831
In a python3.x world, you should probably use a Signature
object:
import inspect
def get_default_args(func):
signature = inspect.signature(func)
return {
k: v.default
for k, v in signature.parameters.items()
if v.default is not inspect.Parameter.empty
}
The args/defaults can be combined as:
import inspect
a = inspect.getargspec(eat_dog)
zip(a.args[-len(a.defaults):],a.defaults)
Here a.args[-len(a.defaults):]
are the arguments with defaults values and obviously a.defaults
are the corresponding default values.
You could even pass the output of zip
to the dict
constructor and create a mapping suitable for keyword unpacking.
looking at the docs, this solution will only work on python2.6 or newer since I assume that inspect.getargspec
returns a named tuple. Earlier versions returned a regular tuple, but it would be very easy to modify accordingly. Here's a version which works with older (and newer) versions:
import inspect
def get_default_args(func):
"""
returns a dictionary of arg_name:default_values for the input function
"""
args, varargs, keywords, defaults = inspect.getargspec(func)
return dict(zip(args[-len(defaults):], defaults))
Come to think of it:
return dict(zip(reversed(args), reversed(defaults)))
would also work and may be more intuitive to some people.
Upvotes: 172
Reputation: 2414
to take care of keyword-only args (and because defaults and kwonlydefaults can be None
):
spec = inspect.getfullargspec(func)
defaults = dict(zip(spec.args[::-1], (spec.defaults or ())[::-1]))
defaults.update(spec.kwonlydefaults or {})
Upvotes: 2
Reputation: 137310
You can use inspect
module with its getargspec
function:
inspect.getargspec(func)
Get the names and default values of a Python function’s arguments. A
tuple
of four things is returned:(args, varargs, keywords, defaults)
.args
is a list of the argument names (it may contain nested lists).varargs
andkeywords
are the names of the*
and**
arguments orNone
.defaults
is a tuple of default argument values orNone
if there are no default arguments; if this tuple hasn
elements, they correspond to the lastn
elements listed inargs
.
See mgilson's answer for exact code on how to retrieve argument names and their default values.
Upvotes: 10