如何检查可选函数参数是否设置

在 Python 中有没有一种简单的方法来检查可选参数的值是否来自它的默认值,或者因为用户已经在函数调用中显式地设置了它?

110625 次浏览

没有。标准的方法是使用用户不会传递的默认值,例如 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参数。

我同意波动性的评论,但是你可以用下面的方式检查:

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

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()

产出:

passed default
z
passed different
b
not passed
z

这个,就像我说的,相当怪异,但它确实起作用了。然而,这是相当不可读的,类似于 Ecatmur建议将不会被自动记录。

下面的函数修饰符 explicit_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)

我有时使用通用唯一字符串(如 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的值时,它没有被传入。

我已经见过这种模式好几次了(例如,库 unittestpy-flagsjinja) :

class Default:
def __repr__( self ):
return "DEFAULT"


DEFAULT = Default()

... 或者类似的俏皮话..:

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.

你可以从 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}

Https://docs.python.org/3/library/inspect.html

很多答案都有一小部分完整的信息,所以我想把它们和我最喜欢的图案放在一起。

默认值为 mutable类型

如果默认值是一个可变对象,那么您很幸运: 您可以利用这样一个事实: 在定义函数时,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')

不变的默认参数

现在,如果默认值是 immutable(并且记住,即使是字符串也是不可变的!) ,那么就不那么优雅了因为你不能利用这个技巧,但是你仍然可以做一些事情,仍然利用可变类型; 基本上你把一个 易变的“假”的默认值放在函数签名中,和所需的“真”的默认值放在函数体中。

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是不可变的,那么感觉特别有趣,所以... ... 您仍然需要显式地使用可变量作为函数默认参数,并在代码中切换到 Nothing。

对不可变的默认值使用 Default

或者,类似于@c-z 的建议,如果 python 文档不够的话: ——) ,你可以在两者之间添加一个对象,使 API 更加明确(不需要阅读文档) ; used _ xy _ 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

The above works nicely also for Default(None).

其他模式

显然,上面的模式看起来比他们应该的丑陋,因为所有的 print只是为了显示他们是如何工作的。除此之外,我发现它们足够简洁和可重复。

你可以编写一个装饰器来添加@dmg 建议的 __call__模式,但是这仍然需要在函数定义本身使用一些奇怪的技巧——如果你的代码需要区分它们,你需要分离出 valuevalue_default,所以我看不出有什么好处,我不会编写这个例子: -)

可变类型作为 Python 中的默认值

更多一点关于 # 1巨蟒抓到你了!,滥用自己的快乐以上。 你可以通过以下步骤看到由于 evaluation at definition而发生的情况:

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 :-) ).

请记住,在 Python 中只有3个可变的 built-in types: setlistdict; 其他所有东西——甚至是字符串!是不可改变的。

@ Ellioh 的答案在 python2中可以工作。在 python3中,以下代码应该可以工作:

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)具有更好的可读性。

这是 Stefano 答案的一个变体,但我发现它更具可读性:

not_specified = {}


def foo(x=not_specified):
if x is not_specified:
print("not specified")
else:
print("specified")