首页 文章

如果它们是None,则调用没有可选参数的函数

提问于
浏览
23

有一个带有可选参数的函数 .

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为None,则调用alpha(“FOO”) . 否则,调用alpha(“FOO”,myp2) .

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

How can I concisely (but readably) make this distinction?

一种可能性通常是check for None within alpha,这将被鼓励,因为这将使代码更安全 . 但是假设 alpha 用于实际上应该处理 None 的其他地方 .

I'd like to handle that on the caller-side .

一种可能性是区分案例:

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

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

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

我正在使用python 3.4,但如果你的答案可以提供适用于任何python版本的好方法,那将是最好的 .


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

6 回答

  • 9

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

    kwargs = dict(p1='FOO', p2=None)
    
    alpha(**{k: v for k, v in kwargs.items() if v is not None})
    
  • 9

    虽然**绝对是一种语言功能,但它肯定不是为解决这个特殊问题而创建的 . 你的建议有效,我的也是 . 哪一个更好用取决于OP代码的其余部分 . 但是,仍然无法编写f(x或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
    

    这可能并不完美,但似乎有效:它是's a function that you can call with another function and it'的参数,它应用deceze's answer来过滤掉 None 的参数 .

  • 2

    但是假设alpha实际上应该用于处理None的其他地方 .

    为了回应这个问题,我知道有一个类似 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 的人肯定打算"default argument"行为,而不是通过 None 的人可能打算将该值解释为文字 None .

  • 2

    不幸的是,没有办法做你想做的事 . 即使广泛采用的python库/框架也使用您的第一种方法 . 这是一行额外的代码,但它非常易读 .

    不要使用 alpha("FOO", myp2 or "bar") 方法,因为正如你自己提到的那样,它会产生一种可怕的耦合,因为它需要调用者知道有关函数的细节 .

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

  • 27

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

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

    我很惊讶没有人提起这件事

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

    您将“无”分配给p2作为标准(或者不是,但这样您在代码中的某一点上具有真正的标准)并使用内联if . Imo最狡猾的答案 . 想到的另一件事是使用包装器,但这样会更不易读 .

    编辑:我可能会做的是使用虚拟值作为标准值并检查它 . 所以这样的事情:

    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
    

相关问题