Get a function argument's default value?
Asked Answered
C

7

105

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.

Comfit answered 27/9, 2012 at 17:40 Comment(0)
D
167

Python3.x

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
    }

Python2.x (old answer)

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.


Dissimulate answered 27/9, 2012 at 17:46 Comment(5)
@Tadeck -- I had to upvote yours. I was just guessing how those could be combined until I saw your post which confirmed my suspicions.Dissimulate
@Tadeck could you explain how this works? I'm a bit perplexed at what's going on.Comfit
@Comfit -- do you know the zip function? What part don't you understand. I'll happily improve the answer if you can tell me what's confusing to you.Dissimulate
@Dissimulate I think I get it now. Args with default values cannot be interspersed with args without default values, so mapping the default values to the args list, starting at the last argument and moving backwards, correctly connects the two.Comfit
@Comfit -- Precisely. I couldn't have stated it nicer myself.Dissimulate
M
13

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 
Myelitis answered 28/1, 2020 at 10:15 Comment(1)
(How) can you tell from this which parameter the default True corresponds to?Mixie
D
13

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
Dameron answered 28/4, 2020 at 13:30 Comment(1)
How does it work if I have more default params?Bentwood
T
10

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 and keywords are the names of the * and ** arguments or None. defaults is a tuple of default argument values or None if there are no default arguments; if this tuple has n elements, they correspond to the last n elements listed in args.

See mgilson's answer for exact code on how to retrieve argument names and their default values.

Trypanosomiasis answered 27/9, 2012 at 17:46 Comment(1)
Deprecated since 3.0, so actually already deprecated for 4 years at the time this answer was written.Compression
P
2

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 {})
Privation answered 13/12, 2016 at 19:5 Comment(1)
Yep. +1 for pointing this out. My original answer was written before python3.x was all that popular. It turns out there's even a better way to handle this now with Signature objects (which I've added to my original answer)Dissimulate
N
2

You can get this 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.

  • .__code__.co_varnames: A tuple of all input variables
  • .__defaults__: A tuple of the default values
    • It is worth noting that this tuple only incudes the default provided variables which must always be positioned last in the function arguments

You can use these two items to match the last n variables in the .__code__.co_varnames with all the items in the .__defaults__

EDIT Thanks to @griloHBG - Added if statement to prevent exceptions when no defaults are specified.

def my_fn(a, b=2, c='a'):
    pass

def get_defaults(fn):
    if fn.__defaults__==None:
        return {}
    return dict(zip(
        fn.__code__.co_varnames[-len(fn.__defaults__):],
        fn.__defaults__
    ))

print(get_defaults(my_fn))

Should give:

{'b': 2, 'c': 'a'}
Nationalism answered 15/4, 2022 at 13:10 Comment(3)
Why the minus 1 with no explanation?Nationalism
__defaults__ is a list of strings that has len equals to amount of arguments with default values. Since in Python arguments with default values must always be the last ones, using - on len of __defaults__ (with the : to get until the end of the array) will bring up the argument names (from co_varnames) that have default values. BUT the function get_defaults will fail if fn has no arguments with default values. Checking on len of __defaults__ would be helpful to prevent errors: if __defaults__'s len is zero then return.Richerson
Thanks. Although there is an issue if you need to capture the rest of kwargs. Will add padding to the right. def my_fn(a, b=2, c='a', **others):Coextensive
P
0

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.

Pratique answered 22/11, 2014 at 2:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.