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

时间:2013-02-07 10:52:06

标签: python function optional-parameters

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

10 个答案:

答案 0 :(得分:43)

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

答案 1 :(得分:11)

以下函数装饰器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)

答案 2 :(得分: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的值时,它没有传入。

答案 3 :(得分:4)

很多答案都没有完整的信息,因此我想将它们与我最喜欢的模式结合在一起。

默认值为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是不可变的,因此...

Default类用于不可变的默认值

或类似于@ c-z的建议,如果python文档不够用:-),则可以在两者之间添加一个对象以使API更显式,而无需阅读文档:

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__模式,但这仍然必须在函数定义本身中使用怪异的技巧-您需要将{{ 1}}和value,如果您的代码需要区分它们,那么我看不出太多优势,因此我不会编写示例:-)

可变类型为默认值

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

value_default

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

请记住,在Python中有3个可变的built-in typestestme()setlist,但其他所有内容-甚至字符串! -是一成不变的。

答案 4 :(得分:2)

我同意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

答案 5 :(得分:2)

我已经多次看过这种模式(例如图书馆unittestpy-flagsjinja):

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

DEFAULT = Default()

......或等效的单行......:

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

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

答案 6 :(得分:2)

@Ellioh的答案在python 2中有效。在python 3中,以下代码应该有效:

import inspect
def explicit_checker(f):
    varnames = inspect.getfullargspec(f)[0]
    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)具有更好的可读性。

答案 7 :(得分:2)

这是stefano回答的一种变体,但我发现它更具可读性:

not_specified = {}

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

答案 8 :(得分:1)

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

答案 9 :(得分:0)

一种奇怪的方法是:

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

现在,正如我所提到的那样,这非常奇特,但它确实起到了作用。然而,这是不可读的,与ecatmursuggestion类似,不会自动记录。