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

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

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")不同。

我怎样才能简洁(但可读)地做出这种区分呢

一种可能性通常是对 检查 alpha内无,这将是鼓励的,因为这将使代码更安全。但是假设 alpha在其他地方使用,在那里它实际上应该像它那样处理 None

我想在呼叫端处理这个问题。

一种可能性是进行案例区分:

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

但是当有多个这样的参数时,它可以很快变成很多代码

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

我使用的是 python 3.4,但是如果您的答案能够提供一个在任何 python 版本中都能正常工作的好方法,那就再好不过了。


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

43524 次浏览

以 kwargs 形式从字典中传递参数,从中过滤出 None值:

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


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

不幸的是,你不可能做你想做的事。即使是被广泛采用的 Python 库/框架也使用您的第一种方法。这是一行额外的代码,但它是相当可读的。

不要使用 alpha("FOO", myp2 or "bar")方法,因为正如您所提到的,它会创建一种可怕的耦合,因为它要求调用者了解函数的细节。

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

您可以通过 alpha.__defaults__检查默认值,然后使用它们代替 None。这样就可以绕过默认值的硬编码:

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

尽管 * * 绝对是一种语言特性,但它肯定不是为解决这个特定问题而创建的。你的建议行得通,我的也行得通。哪一个更好地工作取决于 OP 的其余代码。但是,还是没有办法写 f(x or dont_pass_it_at_all)吗 - < a href = “ https://stackoverflow. com/questions/52494128/call-function-without-option-words-if-they-are-none/52494369? noredirect = 1 # comment91929619 _ 52494329”> 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 的回答来过滤掉 None的参数。

但是假设 alpha 用于其他地方,在那里它实际上应该处理 Nothing。

为了回应这个问题,我已经知道有一个类似于 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是为 快递创建的一个特殊的虚构值,任何通过 _novalue的人都肯定打算采用“默认参数”行为,而通过 None的人可能打算将该值解释为字面 None

我很惊讶居然没人提起这事

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

您可以将 Nothing 赋值给 p2作为标准(或者不赋值,但是这样您在代码中的某一点就有了真正的标准) ,并且使用一个 inline if。我给出了最简洁的答案。我想到的另一件事是使用包装器,但是那样的话可读性就会大大降低。

编辑: 我可能会用一个假人作为标准值,然后检查一下:

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

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

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

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


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


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

当额外的参数触发“额外”功能时,这种方法可以很好地工作,因为它可以让您给函数起更具描述性的名称。

具有 True 和/或 False 默认值的布尔参数的函数经常受益于这种方法。

我在调用一些 Swagger 生成的客户端代码时遇到了同样的问题,我无法修改这些代码,如果在调用生成的方法之前没有清理参数,那么 None可能会出现在查询字符串中。最后,我创建了一个简单的 helper 函数:

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

另一种可能性是简单地执行 alpha("FOO", myp2 or "bar"),但这需要我们知道默认值。通常,我可能会使用这种方法,但是稍后我可能会更改 alpha的默认值,然后需要手动更新这个调用,以便仍然使用(新的)默认值调用它。

只要创建一个常量:

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 (dictionary) ,但是有些值不存在:

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


alpha("FOO", work_type)

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

由于我还不能对答案作出评论,我想补充的是,第一个解决方案(解包 kwargs)将很好地适合装潢师如下:

def remove_none_from_kwargs(func):
@wraps(func)
def wrapper(self, *args, **kwargs):
func(self,*args, **{k: v for k, v in kwargs.items() if v is not None})
return wrapper