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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/14749328/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-18 12:19:13  来源:igfitidea点击:

How to check whether optional function parameter is set

pythonfunctionoptional-parameters

提问by Matthias

Is there an easy way in Python to check whether the value of an optional parameter comes from its default value, or because the user has set it explicitly at the function call?

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

采纳答案by Stefano

Lot of answers have little pieces of the full info, so I'd like to bring it all together with my favourite pattern(s).

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

default value is a mutabletype

默认值是一种mutable类型

If the default value is a mutable object, you are lucky: you can exploit the fact that Python's default arguments are evaluated once when the function is defined (some more about this at the end of the answer in the last section)

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

This means you can easily compare a default mutable value using isto see if it was passed as an argument or left by default, as in the following examples as function or method:

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

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

and

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

Immutable default arguments

不可变的默认参数

Now, it's a bit less elegant if your default is expected to be an immutablevalue (and remember that even strings are immutable!) because you can't exploit the trick as it is, but there is still something you can do, still exploiting mutable type; basically you put a mutable"fake" default in the function signature, and the desired "real" default value in the function body.

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

It feels particularly funny if you real default is None, but Noneis immutable so... you still need to explicitly use a mutable as the function default parameter, and switch to None in the code.

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

Using a Defaultclass for immutable defaults

Default类用于不可变的默认值

or, similar to @c-z suggestion, if python docs are not enough :-) , you can add an object in between to make the API more explicit (without reading the docs); the used_proxy_ Default class instance is mutable, and will contain the real default value you want to use.

或者,类似于@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))

now:

现在:

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

以上也适用于Default(None).

Other patterns

其他图案

Obviously the above patterns looks uglier than they should because of all the printwhich are there only for showing how they work. Otherwise I find them terse and repeatable enough.

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

You could write a decorator to add the __call__pattern suggested by @dmg in a more streamlined way, but this will still oblige to use weird tricks in the function definition itself - you would need to split out valueand value_defaultif your code need to distinguish them, so I don't see much advantage and I won't write the example :-)

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

Mutable types as default values in Python

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

A bit more about #1 python gotcha!, abused for your own pleasure above. You can see what happens due to the evaluation at definitionby doing:

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

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

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

Remember that in Python there are only 3 mutable built-in types: set, list, dict; everything else - even strings! - is immutable.

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

回答by ecatmur

Not really. The standard way is to use a default value that the user would not be expected to pass, e.g. an objectinstance:

并不真地。标准方法是使用用户不会通过的默认值,例如一个object实例:

DEFAULT = object()
def foo(param=DEFAULT):
    if param is DEFAULT:
        ...

Usually you can just use Noneas the default value, if it doesn't make sense as a value the user would want to pass.

通常你可以只使用None默认值,如果它作为用户想要传递的值没有意义。

The alternative is to use kwargs:

另一种方法是使用kwargs

def foo(**kwargs):
    if 'param' in kwargs:
        param = kwargs['param']
    else:
        ...

However this is overly verbose and makes your function more difficult to use as its documentation will not automatically include the paramparameter.

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

回答by isedev

I agree with Volatility's comment. But you could check in the following manner:

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

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

回答by dmg

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

Which outputs:

哪些输出:

passed default
z
passed different
b
not passed
z

Now this, as I mentioned, is quite freakish, but it does the job. However this is quite unreadable and similarly to ecatmur's suggestionwon't be automatically documented.

现在,正如我所提到的,这很奇怪,但它确实起作用了。然而,这是非常不可读的,并且与ecatmur建议类似,不会自动记录。

回答by Ellioh

The following function decorator, explicit_checker, makes a set of parameter names of all the parameters given explicitly. It adds the result as an extra parameter (explicit_params) to the function. Just do 'a' in explicit_paramsto check if parameter ais given explicitly.

下面的函数装饰器 ,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)

回答by Jesse B Miller

I sometimes use a universally unique string (like a UUID).

我有时会使用一个普遍唯一的字符串(如 UUID)。

import uuid
DEFAULT = uuid.uuid4()
def foo(arg=DEFAULT):
  if arg is DEFAULT:
    # it was not passed in
  else:
    # it was passed in

This way, no user could even guess the default if they tried so I can be very confident that when I see that value for arg, it was not passed in.

这样,如果用户尝试过,他们甚至无法猜测默认值,因此我可以非常确信,当我看到 的值时arg,它没有被传入。

回答by c z

I've seen this pattern a few times (e.g. library unittest, py-flags, jinja):

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

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

DEFAULT = Default()

...or the equivalent one-liner...:

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

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.

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

回答by Alex

You can check it from foo.__defaults__and foo.__kwdefaults__

你可以从foo.__defaults__和检查它foo.__kwdefaults__

see a simple example bellow

请看下面的简单示例

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

then by using operator =and isyou can compare them

然后通过使用运算符=is您可以比较它们

and for some cases code bellow is enought

对于某些情况,下面的代码就足够了

For example, you need to avoid changing default value then you can check on equality and then copy if so

例如,您需要避免更改默认值,然后您可以检查相等性,然后复制

    def update_and_show(data=Example):
        if data is Example:
            data = copy.deepcopy(data)
        update_inplace(data) #some operation
        print(data)


Also, it is quite convenient to use getcallargsfrom inspectas it returns real arguments with which function will be invoked. You pass a function and args and kwargs to it (inspect.getcallargs(func, /, *args, **kwds)), it will return real method's arguments used for invocation, taking into consideration default values and other stuff. Have a look at an example below.

此外,使用getcallargsfrom非常方便,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

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

回答by R. Yang

@Ellioh's answer works in python 2. In python 3, the following code should work:

@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

This method can keep the argument names and default values (instead of **kwargs) with better readability.

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

回答by lazieburd

This is a variation on stefano's answer, but i find a little more readable:

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

not_specified = {}

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