【问题标题】:Wrap function without clobbering default arguments包装函数而不破坏默认参数
【发布时间】:2014-09-09 15:54:48
【问题描述】:

有没有办法在不隐藏原始调用提供或不提供可选参数的情况下转发函数参数?

def func1(a=x):
    # do stuff

def func2(b=y):
    # pass args to func1 without masking func1 defaults
    return func1(?)

对 func2() 的调用应该导致调用 func1() 时不带参数或至少使用其默认参数,无论它们可能是什么。

以下几乎可以工作,但基本上我不知道 func2 是否有办法确定它的默认值是否被调用。

def func2(b=y):
    # this comes close but what if func2(y) is called?
    if b == y:
        return func1()
    else:
        return func1(b)

【问题讨论】:

  • 如果默认值为1,调用函数传入1或不理会有什么区别?
  • func2 需要在 func1 更新时更新。如果函数是由不同的作者编写的,这可能会出现问题。
  • 为什么?如果我使用frobble = func2(a=9),为什么我会关心func1() 的默认参数将值从9 更改为11
  • 也许我误解了你的第一条评论。有时 func2 需要调用 func1(),不知道 func1 的默认值,而 func2 的默认值可能没有意义,所以两个默认值都起作用。
  • 您的具体用例是什么? func2 应该足够聪明,只将适当的参数传递给func1,并且应该依赖于任何参数的默认值。

标签: python default-arguments


【解决方案1】:

确定参数是否保留的常用方法是使用None 作为默认值。您不太可能使用 None 调用函数,因此它是一个有用的标记。

def func2(b=None):
    if b is None:
        return func1()
    else:
        return func1(b)

【讨论】:

  • 这是一个很好的实用建议,但如果有人愿意深入了解内部结构,是否有确定的方法来确定是否使用了默认值?
  • @Praxeolitic:没有直接的方法可以区分在没有参数(并使用默认值)的情况下调用的函数和在显式传入默认参数的情况下调用的函数。这就是为什么如果您需要特别处理默认情况,使用哨兵值(用户不太可能错误地传递给您)是一个好主意。
  • @Blckknght 我认为您可以使用*args 作为参数列表来做到这一点,但我对此并不熟悉,因此我不会建议将其作为答案。跨度>
  • @MarkRansom:是的,您可以这样处理自己的默认参数,但如果您的签名是def foo(arg="some default"):,则无法从函数内部判断arg是默认值还是调用者实际上明确地传递了默认值。你可能也不应该在意!
【解决方案2】:

我怀疑这样做的正确方法是让您的func2 函数使用一个标记值作为其默认参数,这样您就可以轻松识别它。如果你得到了那个哨兵,你可以设置你想要传递给func1的参数(例如,不传递任何参数)。您可以使用参数解包来处理传递可变数量的参数(例如 0-1)。

一个常见的标记是None,但如果这对调用者来说是一个有意义的值,您可能想要使用其他东西(object 的实例是一个常见的选择)。这是一个例子:

def func1(a="default value"): # lets assume we don't know what this default is
    # do stuff with a

# later, perhaps in a different module

_sentinel = object()    # our sentinel object

def func2(b=_sentinel):
    if b is _sentinel:  # test for the sentinel
        b = "some useful value"
        a_args = ()     # arguments to func1 is an empty tuple
    else:
        a_args = (b,)   # pack b into a 1-tuple

    # do stuff with b perhaps

    func1(*a_args)      # call func1 with appropriate arguments (either b or nothing)

请注意,这种设计比较傻。大多数情况下,您要么在所有情况下使用参数调用func1,要么在所有情况下不使用参数调用它。您很少需要有条件地传递这样的参数。

【讨论】:

    【解决方案3】:

    看到这个答案:

    https://stackoverflow.com/a/2088101/933416

    无法从内部获取您想要的信息。要检测是否使用了默认值,您需要在函数内重新实现内部默认参数处理,即:

    def func2(*args, **kwargs):
        if len(args) == 0 and "b" not in kwargs:
            b = y
            return func1()
        else:
            return func1(b)
    

    现在,从第一次检查开始,我们保证调用的是 func2(),而不是 func2(y)func2(b=y)。几乎在所有情况下,唯一的对象哨兵都足以避免必须真正保证它是如何被调用的,但它可以做到。

    但是从您立即返回 func1 的结果这一事实来看,我看不出func2 甚至有默认参数的理由。在默认调用 (func2()) 中,从不使用 y。那么它为什么会存在呢?为什么不直接使用define func2(*a, **k) 并将它们直接传递给func1

    【讨论】:

    • 在调用func1 之前,可能b 被用于代码的一些未显示部分。此外,作为“内部默认参数处理”的一部分,您应该确保 args 不会太长并且 kwargs 不包含无效参数。对于这样一个简单的任务,它变得太多样板。
    【解决方案4】:

    参数转发应该使用可变参数来完成:

    def func2(*args, **kwargs):
        func1(*args, **kwargs)
    

    一切都会奏效,尽管自省会受到一点影响。


    如果您有时需要不传递参数,则可以随时删除参数:

    del kwargs["name"]
    

    一个例子:

    def print_wrapper(*args, extrabig=False, **kwargs):
        if extrabig:
            args = [arg*2 for arg in args]
            kwargs["sep"] = kwargs.get("sep", " ") * 2
    
        print(*args, **kwargs)
    
    print_wrapper(2, 4, 8, end="!!!\n")
    #>>> 2 4 8!!!
    
    print_wrapper(2, 4, 8, sep=", ", end="!!!\n")
    #>>> 2, 4, 8!!!
    
    print_wrapper(2, 4, 8, extrabig=True, end="!!!\n")
    #>>> 4  8  16!!!
    

    如果你真的不想这样做(虽然你错了),你可以使用object 来生成一个唯一的哨兵。

    # Bad! Won't let you print None
    def optionally_print_one_thing(thing=None):
        if thing is not None:
            print(thing)
    
    # Better
    _no_argument = object()
    def optionally_print_one_thing(thing=_no_argument):
        if thing is not _no_argument:
            print(thing)
    

    【讨论】:

    • 我想我对这个问题的理解与你不同。无论如何,我会扩展它。
    • 也有可能是func2的作者无法控制func1。
    • @Praxeolitic 我不明白为什么这很重要。
    【解决方案5】:

    您的具体用例是什么? func2 应该足够聪明,只将适当的参数传递给 func1,并且应该依赖于任何参数的默认值。

    我唯一一次发现有必要改变func2 调用func1 的方式是func1 是一个带有诡异签名的c 函数:

    def func2(this, that, those=None):
        if those is None:
            return func1(this, that)
        else:
            return func1(this, that, those)
    

    【讨论】:

      猜你喜欢
      • 2017-09-18
      • 1970-01-01
      • 1970-01-01
      • 2015-05-19
      • 2020-09-24
      • 2011-01-25
      • 2017-02-10
      • 1970-01-01
      相关资源
      最近更新 更多