Is there an easy way in Python to check whether the value of an optional parameter comes from its default value, or because the user has set it explicitly at the function call?
Lot of answers have little pieces of the full info, so I'd like to bring it all together with my favourite pattern(s).
default value is a mutable
type
If the default value is a mutable object, you are lucky: you can exploit the fact that Python’s default arguments are evaluated once when the function is defined (some more about this at the end of the answer)
This means you can easily compare a default mutable value using is
to see if it was passed as an argument or left by default, as in the following examples as function or method:
def f(value={}):
if value is f.__defaults__[0]:
print('default')
else:
print('passed in the call')
and
class A:
def f(self, value={}):
if value is self.f.__defaults__[0]:
print('default')
else:
print('passed in the call')
Immutable default arguments
Now, it's a bit less elegant if your default is expected to be an immutable
value (and remember that even strings are immutable!) because you can't exploit the trick as it is, but you can still do something like:
def f(value={}):
"""
my function
:param value: value for my function; default is 1
"""
if value is f.__defaults__[0]:
print('default')
value = 1
else:
print('passed in the call')
# whatever I want to do with the value
print(value)
It feels particularly funny if you real default is None
, but None
is immutable so...
Using a Default
class for immutable defaults
or, similar to @c-z suggestion, if python docs are not enough :-) , you can add an object in between to make the API more explicit without reading the docs:
class Default:
def __repr__(self):
return "Default Value: {} ({})".format(self.value, type(self.value))
def __init__(self, value):
self.value = value
def f(default=Default(1)):
if default is f.__defaults__[0]:
print('default')
print(default)
default = default.value
else:
print('passed in the call')
print("argument is: {}".format(default))
now:
>>> f()
default
Default Value: 1 (<class 'int'>)
argument is: 1
>>> f(2)
passed in the call
argument is: 2
The above works nicely also for Default(None)
.
Other patterns
Obviously the above patterns looks uglier than they should because of all the print
which are there only for showing how they work. Otherwise I find them terse and repeatable enough.
You could write a decorator to add the __call__
pattern suggested by @dmg in a more streamlined way, but this will still oblige to use weird tricks in the function definition itself - you would need to split out value
and value_default
if your code need to distinguish them, so I don't see much advantage and I won't write the example :-)
Mutable types as default values
A bit more about #1 python gotcha!, abused for your own pleasure above. You can see what happens due to the evaluation at definition by doing:
def testme(default=[]):
print(id(default))
You can run testme()
as many time as you want, you will always see a reference to the same default instance (so basically your default is immutable :-) ).
Remember that in Python there are 3 mutable built-in types: set
, list
, dict
, but everything else - even strings! - is immutable.
Not really. The standard way is to use a default value that the user would not be expected to pass, e.g. an object
instance:
DEFAULT = object()
def foo(param=DEFAULT):
if param is DEFAULT:
...
Usually you can just use None
as the default value, if it doesn't make sense as a value the user would want to pass.
The alternative is to use kwargs
:
def foo(**kwargs):
if 'param' in kwargs:
param = kwargs['param']
else:
...
However this is overly verbose and makes your function more difficult to use as its documentation will not automatically include the param
parameter.
The following function decorator, explicit_checker
, makes a set of parameter names of all the parameters given explicitly. It adds the result as an extra parameter (explicit_params
) to the function. Just do 'a' in explicit_params
to check if parameter a
is given explicitly.
def explicit_checker(f):
varnames = f.func_code.co_varnames
def wrapper(*a, **kw):
kw['explicit_params'] = set(list(varnames[:len(a)]) + kw.keys())
return f(*a, **kw)
return wrapper
@explicit_checker
def my_function(a, b=0, c=1, explicit_params=None):
print a, b, c, explicit_params
if 'b' in explicit_params:
pass # Do whatever you want
my_function(1)
my_function(1, 0)
my_function(1, c=1)
I sometimes use a universally unique string (like a UUID).
import uuid
DEFAULT = uuid.uuid4()
def foo(arg=DEFAULT):
if arg is DEFAULT:
# it was not passed in
else:
# it was passed in
This way, no user could even guess the default if they tried so I can be very confident that when I see that value for arg
, it was not passed in.
I agree with Volatility's comment. But you could check in the following manner:
def function(arg1,...,**optional):
if 'optional_arg' in optional:
# user has set 'optional_arg'
else:
# user has not set 'optional_arg'
optional['optional_arg'] = optional_arg_default_value # set default
I've seen this pattern a few times (e.g. library unittest
, py-flags
, jinja
):
class Default:
def __repr__( self ):
return "DEFAULT"
DEFAULT = Default()
...or the equivalent one-liner...:
DEFAULT = type( 'Default', (), { '__repr__': lambda x: 'DEFAULT' } )()
Unlike DEFAULT = object()
, this assists type-checking and provides information when errors occur -- frequently either the string representation ("DEFAULT"
) or the class name ("Default"
) are used in error messages.
You can check it from foo.__defaults__
and foo.__kwdefaults__
see a simple example bellow
def foo(a, b, c=123, d=456, *, e=789, f=100):
print(foo.__defaults__)
# (123, 456)
print(foo.__kwdefaults__)
# {'e': 789, 'f': 100}
print(a, b, c, d, e, f)
#and these variables are also accessible out of function body
print(foo.__defaults__)
# (123, 456)
print(foo.__kwdefaults__)
# {'e': 789, 'f': 100}
foo.__kwdefaults__['e'] = 100500
foo(1, 2)
#(123, 456)
#{'f': 100, 'e': 100500}
#1 2 123 456 100500 100
then by using operator =
and is
you can compare them
and for some cases code bellow is enought
For example, you need to avoid changing default value then you can check on equality and then copy if so
def update_and_show(data=Example):
if data is Example:
data = copy.deepcopy(data)
update_inplace(data) #some operation
print(data)
A little freakish approach would be:
class CheckerFunction(object):
def __init__(self, function, **defaults):
self.function = function
self.defaults = defaults
def __call__(self, **kwargs):
for key in self.defaults:
if(key in kwargs):
if(kwargs[key] == self.defaults[key]):
print 'passed default'
else:
print 'passed different'
else:
print 'not passed'
kwargs[key] = self.defaults[key]
return self.function(**kwargs)
def f(a):
print a
check_f = CheckerFunction(f, a='z')
check_f(a='z')
check_f(a='b')
check_f()
Which outputs:
passed default
z
passed different
b
not passed
z
Now this, as I mentioned, is quite freakish, but it does the job. However this is quite unreadable and similarly to ecatmur's suggestion won't be automatically documented.
来源:https://stackoverflow.com/questions/14749328/python-how-to-check-whether-optional-function-parameter-is-set