104

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

4

10 回答 10

64

并不真地。标准方法是使用用户不希望通过的默认值,例如一个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参数。

于 2013-02-07T10:54:58.347 回答
26

很多答案都包含完整信息的一小部分,所以我想将它们与我最喜欢的模式结合在一起。

默认值是一种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因为您不能按原样利用这个技巧,但是您仍然可以做一些事情,仍然利用 mutable类型; 基本上,您在函数签名中放置了一个可变的“假”默认值,并在函数体中放置了所需的“真实”默认值。

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它是不可变的,所以......你仍然需要明确地使用一个可变的作为函数默认参数,并在代码中切换到无。

使用Default不可变默认值的类

或者,类似于@cz 建议,如果 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这些只是为了展示它们是如何工作的。否则,我发现它们足够简洁和可重复。

您可以编写一个装饰器以__call__更简化的方式添加@dmg 建议的模式,但这仍然需要在函数定义本身中使用奇怪的技巧 - 您需要拆分value并且value_default如果您的代码需要区分它们,所以我没有看到太多优势,我不会写这个例子:-)

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

关于#1 python gotcha 的更多信息!,滥用上面为你自己的乐趣。您可以通过执行以下操作查看由于定义时的评估而发生的情况:

def testme(default=[]):
    print(id(default))

您可以testme()根据需要运行任意多次,您将始终看到对相同默认实例的引用(因此基本上您的默认值是不可变的 :-))。

请记住,在 Python 中只有 3 种可变的内置类型set, list, dict; 其他一切——甚至是字符串!- 是不可变的。

于 2019-08-23T15:03:07.510 回答
16

下面的函数装饰器,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)
于 2013-02-07T11:23:29.883 回答
5

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

于 2015-10-15T21:54:08.187 回答
5

我见过这种模式几次(例如 library unittest, py-flags, jinja):

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

DEFAULT = Default()

...或等效的单线...:

DEFAULT = type( 'Default', (), { '__repr__': lambda x: 'DEFAULT' } )()

与 不同DEFAULT = object()的是,这有助于类型检查并在发生错误时提供信息——通常在错误消息中使用字符串表示 ( "DEFAULT") 或类名 ( )。"Default"

于 2017-12-08T11:20:05.253 回答
5

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

于 2019-09-30T11:16:14.517 回答
4

我同意波动率的评论。但是您可以通过以下方式进行检查:

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
于 2013-02-07T10:56:42.287 回答
4

您可以从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)

getcallargs此外,使用from非常方便,inspect因为它返回将调用函数的真实参数。您将一个函数以及 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

于 2018-03-31T16:08:04.913 回答
2

这是 stefano 答案的变体,但我发现更具可读性:

not_specified = {}

def foo(x=not_specified):
    if x is not_specified:
            print("not specified")
    else:
            print("specified")
于 2020-03-31T13:52:53.523 回答
1

一个有点奇怪的方法是:

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建议类似,不会自动记录。

于 2013-02-07T11:14:36.673 回答