在Python中,是否有一种简单的方法可以检查可选参数值是来自其默认值,还是因为用户在函数调用时显式设置了它?
在Python中,是否有一种简单的方法可以检查可选参数值是来自其默认值,还是因为用户在函数调用时显式设置了它?
不是真的。标准的方法是使用一个默认值,用户不需要传入这个值,例如一个object
实例:
DEFAULT = object()
def foo(param=DEFAULT):
if param is DEFAULT:
...
通常情况下,您可以将None
用作默认值,如果它不是用户想要传递的值,则没有意义。
另一种选择是使用kwargs
:
def foo(**kwargs):
if 'param' in kwargs:
param = kwargs['param']
else:
...
然而,这种写法过于冗长,会使你的函数难以使用,因为它的文档不会自动包括param
参数。
Ellipsis
单例作为默认值,它专门设计用作“跳过此项”的值。...
是 Ellipsis
的别名,因此希望使用位置参数的用户只需调用 your_function(p1, ..., p3)
,这使得代码易于阅读和理解。 - Bachsauis
而不是==
。 - undefined很多答案只提供了部分信息,所以我想用我最喜欢的模式将它们整合在一起。
如果默认值是可变对象,那么你很幸运:你可以利用Python默认参数的特性,在函数定义时仅计算一次(有关此问题的更多内容请参见最后一节中的答案)
这意味着您可以轻松地使用 is
来比较默认的可变值,以查看它是否作为参数传递或以默认方式留下,例如以下示例作为函数或方法:
def f(value={}):
if value is f.__defaults__[0]:
print('default')
else:
print('passed in the call')
和
class A:
def f(self, value={}):
if value is self.f.__defaults__[0]:
print('default')
else:
print('passed in the call')
现在,如果你的默认值预期是一个不可变
的值(记住,即使字符串也是不可变的!),那么这个技巧就不太优雅了,因为你无法像之前那样利用这个技巧。但仍然有一些事情可以做,仍然可以利用可变类型;基本上,在函数签名中放置一个可变的“虚假”默认值,并将所需的“真实”默认值放在函数体中。
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)
如果您的默认值是None
,那么感觉特别有趣,但是None
是不可变的,所以...您仍然需要明确地使用一个可变作为函数默认参数,并在代码中切换到None
。
Default
类或者,与@c-z的建议类似,如果Python文档不足够用 :-),您可以添加一个对象在其中,以使API更加明确(而无需阅读文档);使用的used_proxy_
Default类实例是可变的,并将包含您想要使用的真实默认值。
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))
现在:
>>> f()
default
Default Value: 1 (<class 'int'>)
argument is: 1
>>> f(2)
passed in the call
argument is: 2
对于Default(None)
,上述方法同样适用。
显然,由于所有的print
只是为了展示它们的工作原理,上述模式看起来比它们应该更丑陋。除此之外,我认为它们足够简洁和可重复。
你可以编写一个装饰器以更流畅的方式添加@dmg
建议的__call__
模式,但这仍然会强制在函数定义本身中使用奇怪的技巧 - 如果您的代码需要区分value
和value_default
,则需要将它们拆分出来,因此我不认为有多大优势,并且我不会写例子 :-)
关于#1 python gotcha!(python中容易犯的错误),上面滥用了它,更多信息请查看在定义时进行评估:
def testme(default=[]):
print(id(default))
您可以随时运行testme()
,您将始终看到对同一默认实例的引用(因此基本上您的默认设置是不可变的 :-))。
请记住,在Python中只有3种可变的内置类型: set
,list
,dict
; 其他所有内容 - 甚至字符串! - 都是不可变的。
1
,应该是不可变的... - Stefano1
。如果在解释中没有表述清楚,请原谅,但回应部分的整个重点是为了能够拥有不可变的默认值(1
)。如果您检查示例,您会看到它说:print('default'); value = 1
,而不是value={}
。 - Stefanoexplicit_checker
,可以将所有显式指定的参数名字组成一个集合。它会将结果添加为额外的参数(explicit_params
)传递给函数。只需执行'a' in explicit_params
来检查是否显式指定了参数a
。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)
我曾多次看到这种模式(例如库 unittest
,py-flags
,jinja
):
class Default:
def __repr__( self ):
return "DEFAULT"
DEFAULT = Default()
...或等效的一行代码...
DEFAULT = type( 'Default', (), { '__repr__': lambda x: 'DEFAULT' } )()
与DEFAULT = object()
不同的是,这种方法有助于类型检查并在出现错误时提供信息——通常在错误消息中使用字符串表示形式("DEFAULT"
)或类名("Default"
)。
有时我会使用通用唯一标识符(例如UUID)。
import uuid
DEFAULT = uuid.uuid4()
def foo(arg=DEFAULT):
if arg is DEFAULT:
# it was not passed in
else:
# it was passed in
这样做,即使用户试图猜测默认值,也不可能猜中,因此我非常有信心当我看到arg
的这个值时,它没有被传递。
object()
代替uuid4()
- 它仍然是一个独特的实例,这就是is
检查的内容。 - c z@Ellioh的答案适用于Python 2。在Python 3中,下面的代码应该可以工作:
import inspect
from functools import wraps
def explicit_checker(f):
varnames = inspect.getfullargspec(f)[0]
@wraps(f)
def wrapper(*a, **kw):
kw['explicit_params'] = set(list(varnames[:len(a)]) + list(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
这种方法可以保留参数名称和默认值(而不是 **kwargs),提高可读性。
foo.__defaults__
和 foo.__kwdefaults__
来查看它。以下是一个简单的示例: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
然后,通过使用运算符=
和is
,您可以将它们进行比较。
对于某些情况下下面的代码就足够了。
例如,如果您需要避免更改默认值,则可以比较它们是否相等,如果相等则进行复制。
def update_and_show(data=Example):
if data is Example:
data = copy.deepcopy(data)
update_inplace(data) #some operation
print(data)
inspect
中的getcallargs
非常方便,因为它返回函数将被调用时使用的实际参数。你只需要将函数、args和kwargs传递给它(inspect.getcallargs(func, /, *args, **kwds)
),它将返回实际方法调用所使用的参数,考虑到默认值和其他内容。请看下面的例子。from inspect import getcallargs
# we have a function with such signature
def show_params(first, second, third=3):
pass
# if you wanted to invoke it with such params (you could get them from a decorator as example)
args = [1, 2, 5]
kwargs = {}
print(getcallargs(show_params, *args, **kwargs))
#{'first': 1, 'second': 2, 'third': 5}
# here we didn't specify value for d
args = [1, 2, 3, 4]
kwargs = {}
# ----------------------------------------------------------
# but d has default value =7
def show_params1(first, *second, d = 7):
pass
print(getcallargs(show_params1, *args, **kwargs))
# it will consider b to be equal to default value 7 as it is in real method invocation
# {'first': 1, 'second': (2, 3, 4), 'd': 7}
# ----------------------------------------------------------
args = [1]
kwargs = {"d": 4}
def show_params2(first, d=3):
pass
print(getcallargs(show_params2, *args, **kwargs))
#{'first': 1, 'd': 4}
我赞同 Volatility 的评论。但你可以通过以下方式进行检查:
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
def func(optional=value)
而不是 **kwargs
。 - Zaur Nasibov**kwargs
的目的有点不同。顺便说一下,-1没问题 :) 我对你的-1是意外的 :) - Zaur Nasibovnot_specified = {}
def foo(x=not_specified):
if x is not_specified:
print("not specified")
else:
print("specified")
foo(x: dict = not_specified) -> None
。默认使用的虚拟值将其类型赋予参数。如果您的函数具有参数化类型,则无法正常工作:foo(x: T = not_specified)
; "Incompatible default for argument "x" (default has type "Dict[Any, Any]", argument has type "T")
"。您可以使用 Union[T, dict]
,但这会使代码变得复杂。 - bfontainemypy
是一个包,不是Python本身。同时,请注意类型提示是语言的可选功能。如果您为mypy
工作或希望自己的代码使用并符合mypy
,那么这是否会导致类型问题就很重要了。如果不是,则无关紧要。 - hlongmoremypy
是一个包,不是Python本身。同时,请注意类型提示是语言的一个可选功能。如果您在为mypy
工作,或希望您自己的代码使用并符合mypy
,那么这是否会导致类型方面的问题就很重要了。如果不是这样,那么就无关紧要。 - undefinedclass 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()
输出结果为:
passed default
z
passed different
b
not passed
z
check_f('z')
的行为,正如您所说的那样,这也是奇特的。 - Michael J. Barber
None
用作默认值并进行检查即可。如果您真的可以设置此测试,则还应排除用户明确传递调用默认行为的值的任何可能性。 - Michael J. BarberClass My(): def __init__(self, _p=None, a=True, b=True, c=False)
用户调用它时使用x=My(b=False)
。如果函数可以检测到b未明确设置且未设置变量从顶层传递,则类方法可以使用x=My(_p=self, c=True)
进行自我调用。但是,如果它们无法检测到,则递归调用必须显式传递每个变量:x=My(a=self.a, b=self.b, c=True, d=self.d, ...)
。 - Davex=My()
和x=My(a=True)
。您的情况涉及将可选参数赋值为非默认值。 - Volatility