如果没有参数,则调用不带可选参数的函数

时间:2018-09-25 08:50:33

标签: python python-3.x

有一个函数带有可选参数。

def alpha(p1="foo", p2="bar"):
     print('{0},{1}'.format(p1, p2))

让我迭代一下当我们以不同方式使用该函数时会发生什么:

>>> alpha()
foo,bar
>>> alpha("FOO")
FOO,bar
>>> alpha(p2="BAR")
foo,BAR
>>> alpha(p1="FOO", p2=None)
FOO,None

现在考虑我想像alpha("FOO", myp2)myp2这样称呼的情况,要么包含要传递的值,要么就是None。但是,即使该函数处理p2=None,我还是希望它使用其默认值"bar"
也许这句话令人困惑,所以让我改写一下:

  

如果myp2 is None,请致电alpha("FOO")。否则,请致电alpha("FOO", myp2)

该区别是相关的,因为alpha("FOO", None)alpha("FOO")的结果不同。

我如何简明(但易于理解)做出这种区分?

一种可能通常是使用check for None within alpha,这会受到鼓励,因为这样做会使代码更安全。但是假设alpha在实际上应该像处理None的其他地方使用。

我想在呼叫方进行处理

一种可能是区分大小写:

if myp2 is None:
    alpha("FOO")
else:
    alpha("FOO", myp2)

但是,当有多个这样的参数时,这很快就会变成很多代码。 (指数为2 ^ n)

另一种可能性是只做alpha("FOO", myp2 or "bar"),但是这要求我们知道默认值。通常,我可能会采用这种方法,但以后可能会更改alpha的默认值,然后需要手动更新此调用,以便仍使用(新)默认值来调用它。 / p>

我正在使用python 3.4,但最好是您的答案可以提供适用于任何python版本的好方法。


问题在这里已经在技术上解决了,但是我再次提出了一些要求,因为第一个答案掩盖了这一点: 我希望将alpha的行为与默认值"foo", "bar"一起保留下来,因此(可能)不是更改alpha本身的选择。
换句话说,假设alpha在其他地方用作alpha("FOO", None),其中预期输出FOO,None的行为。

9 个答案:

答案 0 :(得分:28)

将参数作为kwargs从字典中传递,从中过滤出None值:

kwargs = dict(p1='FOO', p2=None)

alpha(**{k: v for k, v in kwargs.items() if v is not None})

答案 1 :(得分:9)

  

尽管**绝对是一种语言功能,但肯定不是为了解决此特定问题而创建的。您的建议有效,我的建议也有效。哪一种效果更好取决于OP的其余代码。但是,仍然没有办法写f(x or dont_pass_it_at_all)   -blue_note

感谢您的精彩回答,我想我会尽力做到这一点:

# gen.py
def callWithNonNoneArgs(f, *args, **kwargs):
    kwargsNotNone = {k: v for k, v in kwargs.items() if v is not None}
    return f(*args, **kwargsNotNone)

# python interpreter
>>> import gen
>>> def alpha(p1="foo", p2="bar"):
...     print('{0},{1}'.format(p1,p2))
...
>>> gen.callWithNonNoneArgs(alpha, p1="FOO", p2=None)
FOO,bar
>>> def beta(ree, p1="foo", p2="bar"):
...     print('{0},{1},{2}'.format(ree,p1,p2))
...
>>> beta('hello', p2="world")
hello,foo,world
>>> beta('hello', p2=None)
hello,foo,None
>>> gen.callWithNonNoneArgs(beta, 'hello', p2=None)
hello,foo,bar

这可能并不完美,但似乎可以正常工作:您可以使用另一个函数及其参数来调用该函数,并应用deceze's answer来过滤掉None的参数。

答案 2 :(得分:9)

  

但是,假定在实际应该处理None的其他地方使用了alpha。

为了回应这一担忧,众所周知,我有一个类似None的值,实际上并没有None

_novalue = object()

def alpha(p1=_novalue, p2=_novalue):
    if p1 is _novalue:
        p1 = "foo"
    if p2 is _novalue:
        p2 = "bar"
    print('{0},{1}'.format(p1, p2))

现在参数仍然是可选的,因此您可以忽略传递它们中的任何一个。并且该函数正确处理None。如果您想明确不传递参数,则可以传递_novalue

>>> alpha(p1="FOO", p2=None)
FOO,None
>>> alpha(p1="FOO")
FOO,bar
>>> alpha(p1="FOO", p2=_novalue)
FOO,bar

并且由于_novalue是为此 express 目的创建的特殊虚构值,因此,通过_novalue的任何人肯定都打算使用“默认参数”行为,而不是给通过None的人,该人可能打算将该值解释为文字None

答案 3 :(得分:2)

很遗憾,您无法做您想做的事。甚至被广泛采用的python库/框架也使用您的第一种方法。这是额外的一行代码,但是可读性很强。

请勿使用alpha("FOO", myp2 or "bar")方法,因为就像您自己提到的那样,它会产生一种可怕的耦合,因为它要求调用者知道有关该函数的详细信息。

关于变通方法:您可以为函数创建一个装饰器(使用inspect模块),该装饰器检查传递给它的参数。如果其中之一是None,它将用其自己的默认值替换该值。

答案 4 :(得分:2)

您可以通过alpha.__defaults__检查默认值,然后使用它们代替None。这样一来,您就可以避免对默认值进行硬编码:

>>> args = [None]
>>> alpha('FOO', *[x if x is not None else y for x, y in zip(args, alpha.__defaults__[1:])])

答案 5 :(得分:1)

我很惊讶没有人提出这一点

def f(p1="foo", p2=None):
    p2 = "bar" if p2 is None else p2
    print(p1+p2)

您将p2分配为None(或不分配)(或不这样做,但是这样一来,您在代码中的某一点就拥有了真正的标准)并使用内联if。伊莫最pythonic的答案。我想到的另一件事是使用包装器,但是这样会降低可读性。

编辑: 我可能要做的是使用一个虚拟值作为标准值并进行检查。像这样:

class dummy():
    pass

def alpha(p1="foo", p2=dummy()):
    if isinstance(p2, dummy):
        p2 = "bar"
    print("{0},{1}".format(p1, p2))

alpha()
alpha("a","b")
alpha(p2=None)

产生:

foo,bar
a,b
foo,None

答案 6 :(得分:1)

不是直接的答案,但是我认为这值得考虑:

查看是否可以将您的函数分为几个函数,两个函数都没有任何默认参数。将所有共享功能分解为您指定为内部的功能。

def alpha():
    _omega('foo', 'bar')

def beta(p1):
    _omega(p1, 'bar')

def _omega(p1, p2):
     print('{0},{1}'.format(p1, p2))

当额外的参数触发“额外”功能时,此方法效果很好,因为它可能使您可以为函数赋予更多描述性名称。

带有布尔参数且默认值为True和/或False的函数通常会从这种方法中受益。

答案 7 :(得分:1)

在调用一些Swagger生成的客户端代码时,我遇到了同样的问题,我无法修改,如果我在调用生成的方法之前不清理参数,则None可能会以查询字符串结尾。我最终创建了一个简单的辅助函数:

def defined_kwargs(**kwargs):
    return {k: v for k, v in kwargs.items() if v is not None}

>>> alpha(**defined_kwargs(p1="FOO", p2=None))
FOO,bar

对于更复杂的调用,它使内容保持可读性:

def beta(a, b, p1="foo", p2="bar"):
     print('{0},{1},{2},{3}'.format(a, b, p1, p2,))

p1_value = "foo"
p2_value = None

>>> beta("hello",
         "world",
         **defined_kwargs(
             p1=p1_value, 
             p2=p2_value))

hello,world,FOO,bar

答案 8 :(得分:1)

另一种可能性是只做alpha("FOO", myp2 or "bar"),但这需要我们知道默认值。通常,我可能会采用这种方法,但以后可能会更改alpha的默认值,然后需要手动更新此调用,以便仍使用(新)默认值来调用它。 / p>

只需创建一个常量:

P2_DEFAULT = "bar"

def alpha(p1="foo", p2=P2_DEFAULT):
     print('{0},{1}'.format(p1, p2))

并调用函数:

alpha("FOO", myp2 or P2_DEFAULT)

如果alpha的默认值将被更改,我们只需要更改一个常数。

在某些情况下,请谨慎使用逻辑or,请参阅https://stackoverflow.com/a/4978745/3605259

另一个(更好的)用例

例如,我们有一些配置(字典)。但是一些值不存在:

config = {'name': 'Johnny', 'age': '33'}
work_type = config.get('work_type', P2_DEFAULT)

alpha("FOO", work_type)

因此,我们使用dict的方法get(key, default_value),如果我们的配置(dict)不包含这样的键,它将返回default_value