有一个函数带有可选参数。
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
的行为。
答案 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
。