Python:如何检查是否设置了可选的函数参数

Mat*_*ias 68 python function optional-parameters

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

eca*_*mur 49

并不是的.标准方法是使用用户不希望传递的默认值,object例如:

DEFAULT = object()
def foo(param=DEFAULT):
    if param is DEFAULT:
        ...
Run Code Online (Sandbox Code Playgroud)

通常,您可以将其None用作默认值,如果它作为用户想要传递的值没有意义.

另一种方法是使用kwargs:

def foo(**kwargs):
    if 'param' in kwargs:
        param = kwargs['param']
    else:
        ...
Run Code Online (Sandbox Code Playgroud)

然而,这过于冗长,使您的功能更难以使用,因为其文档不会自动包含param参数.

  • 我也看到有几个人使用Ellipsis内置用于需要它的地方,而None被认为是有效的输入.这与第一个示例基本相同. (7认同)

Ste*_*ano 26

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

默认值是一种mutable类型

如果默认值是一个可变对象,那么你很幸运:你可以利用 Python 的默认参数在函数定义时计算一次的事实(在最后一节的答案末尾有更多关于这个的信息)

这意味着您可以轻松地比较默认的可变值,is以查看它是作为参数传递还是默认保留,如下面的函数或方法示例所示:

def f(value={}):
    if value is f.__defaults__[0]:
        print('default')
    else:
        print('passed in the call')
Run Code Online (Sandbox Code Playgroud)

class A:
    def f(self, value={}):
        if value is self.f.__defaults__[0]:
            print('default')
        else:
            print('passed in the call')
Run Code Online (Sandbox Code Playgroud)

不可变的默认参数

现在,如果你的默认值应该是一个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)
Run Code Online (Sandbox Code Playgroud)

如果你真正的默认值是None,那感觉特别有趣,但它None是不可变的,所以......你仍然需要明确地使用一个可变的作为函数默认参数,并在代码中切换到 None 。

Default类用于不可变的默认值

或者,类似于@cz 的建议,如果 python 文档不够 :-) ,您可以在两者之间添加一个对象以使 API 更加明确(无需阅读文档);used_proxy_ 默认类实例是可变的,并将包含您要使用的真实默认值。

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))
Run Code Online (Sandbox Code Playgroud)

现在:

>>> f()
default
Default Value: 1 (<class 'int'>)
argument is: 1

>>> f(2)
passed in the call
argument is: 2
Run Code Online (Sandbox Code Playgroud)

以上也适用于Default(None).

其他图案

显然,上面的模式看起来比它们应该的更丑,因为所有print这些都只是为了展示它们是如何工作的。否则,我发现它们足够简洁且可重复。

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

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

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

def testme(default=[]):
    print(id(default))
Run Code Online (Sandbox Code Playgroud)

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

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


Ell*_*ioh 14

以下函数装饰器生成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)
Run Code Online (Sandbox Code Playgroud)


小智 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
Run Code Online (Sandbox Code Playgroud)

这样,没有用户甚至可以猜测默认值,如果他们尝试了,所以我可以非常自信,当我看到该值时arg,它没有传入.

  • Python 对象是引用,您可以使用 `object()` 而不是 `uuid4()` - 它仍然是一个唯一的 _instance_,这就是 `is` 检查的内容 (5认同)

c z*_*c z 5

我已经多次看到这种模式(例如 library unittest, py-flags, jinja):

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

DEFAULT = Default()
Run Code Online (Sandbox Code Playgroud)

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

DEFAULT = type( 'Default', (), { '__repr__': lambda x: 'DEFAULT' } )()
Run Code Online (Sandbox Code Playgroud)

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


小智 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
Run Code Online (Sandbox Code Playgroud)

这种方法可以保持参数名称和默认值(而不是 **kwargs)具有更好的可读性。