【问题标题】:Pass keyword argument to another function where the keyword has a default value将关键字参数传递给关键字具有默认值的另一个函数
【发布时间】:2015-07-16 01:05:46
【问题描述】:

以下代码的目的是将传递给main 的关键字参数传递给其他调用的函数。但是,如果没有为 key1 参数赋予 main 值,则 f2 中的 key1 的默认值将被 main 传递的 None 的值覆盖。

def main(x, key1=None, key2=None):
    f1(x, key1=key1)
    f2(x, key1=key1, key2=key2)

def f1(x, key1=None): # note key2 is absent
    print(x, key1)

def f2(x, key1=42, key2=None):
    print(x, key1, key2)

main(1)

输出:

(1, None)
(1, None, None)

有没有一种简单的方法可以仅更改main 的定义(可能包括参数),以便调用函数的输出如下所示?

(1, None)
(1, 42, None)

显然,有一些荒谬的方法可以仅更改 main 以创建该输出,但是将逻辑插入 main 以传递特定函数的缺失参数的默认值或更改main 传递的关键字取决于其中哪个是None。所以我不是在寻找这样的解决方案。

解决这个问题的常用方法是在所有函数中使用**kwargs。或者,您可以更改f2 的定义:

def f2(x, key1=None, key2=None):
    if key1 is None:
        key1 = 42
    print(x, key1, key2)

但我想知道是否还有其他选择。

【问题讨论】:

  • 在您设想的方案中,如果用户调用main(1, None)(为key1 显式传递None)会发生什么?
  • 我不明白您为什么将默认值放在 f1 和 f2 中。由于 main 为它们提供了默认值,这些变量将始终具有值。
  • @SBHayes:因为这些函数可能在其他地方被调用,而不仅仅是来自main
  • @BrenBarn:好问题。取决于它的实现方式,那里可能存在一些歧义。老实说,无论在这种情况下的行为如何,拥有一些可以工作的东西会很好。但我不希望有人在main 中将值显式设置为None,因为函数本身就是一个包装器。

标签: python


【解决方案1】:

没有,因为在您调用f2 时,Python 不知道您作为key1 传递的内容是否已传入。例如,您可以这样做:

def main(x, key1=None, key2=None):
    if some.otherCondition():
      key1 = "Blah"
    f2(x, key1=key1, key2=key2)

要按照您的描述进行操作,Python 必须检查 key1 的每一次使用,以查看它是否恰好等于默认值。如果您对 key1 进行任何操作,除非将其直接传递给另一个函数,否则这很容易变得非常混乱。

此外,没有什么特别的理由可以假设您想“忽略”key1 的值,因为它恰好是main 中的默认值。同样可能的是,类似于您的main 的函数可能会被专门编写为用自己的默认值覆盖f2 的默认值,例如:

def read_csv(filename, separator=","):
    read_delimited_file(separator=separator)

def read_delimited_file(filename, separator="\t"):
    # .,..

这里read_csv 的全部意义在于覆盖它调用的函数中的默认分隔符,同时仍然允许用户在调用read_csv 时覆盖它。

换句话说,决定传递哪些参数涉及决策,这是无法回避的事实,您必须在代码中明确实施这些决策。您似乎已经知道几种可能的解决方案,并且您必须使用其中一种。我认为最清晰的方法之一就是创建一个参数字典:

def main(x, key1=None, key2=None):
    kwargs = {}
    if key1 is not None:
        args['key1'] = key1
    if key2 is not None:
        args['key2'] = key2
    f2(x, **kwargs)

(如果您有很多具有相似逻辑的论点,您可以开始使用 locals() 开始变得聪明,以在某种程度上自动执行此操作,但对于几个明确的论点,这样做会更清楚。)

不得不这样做可能看起来很“愚蠢”,但这并不是很愚蠢,因为这不是默认行为应该是的。

【讨论】:

    【解决方案2】:

    原代码中的问题

    之所以出现困难,是因为main 为它调用的两个函数收集关键字参数,但并非所有关键字参数都被其中一个函数接受。如果在调用main 时指定了key2,则main 函数不能使用**kwargs 将单个dict 传递给两个函数而不在f1 中引发错误。

    为了解决这个问题,main 的原始定义手动处理关键字参数,以便仅将必要的参数传递给其他函数。这反过来又会导致“覆盖”被调用函数提供的默认值的问题。

    可能的解决方案

    一种选择是使用自省来定义一个函数,它将main 中的kwargs 子集化为main 调用的每个函数的适当字典。这避免了编写特定于每个函数的逻辑来对字典进行子集化。

    此更改允许main 调用的函数在其定义中显式列出接受的关键字参数,并允许main 在将它们传递给其他函数之前收集所有关键字参数而不设置默认值。

    def kwargs_for_func(func, kwargs, omit_none=False):
        arg_count = func.func_code.co_argcount
        func_kwargs = func.func_code.co_varnames[:arg_count]
        new_kwargs = {}
        for k in kwargs:
            if k not in func_kwargs:
                continue
            val = kwargs[k]
            if not (omit_none and val is None):
                new_kwargs[k] = val
        return new_kwargs
    
    def apply_with_valid_kwargs(func, *args, **kwargs):
        return func(*args, **kwargs_for_func(func, kwargs))
    
    def main(*args, **kwargs):
        apply_with_valid_kwargs(f1, *args, **kwargs)
        apply_with_valid_kwargs(f2, *args, **kwargs)  
    
    def f1(x, key1=None):
        pass # different def to emphasize changed output from f2
    
    def f2(x, key1=42, key2=None):
        print(x, key1, key2)
    
    main(1)            # f2 prints (1, 42, None)
    main(1, key1=None) # f2 prints (1, None, None)
    

    输出:

    (1, 42, None)
    (1, None, None)
    

    由于main 正在收集kwarg 字典,这回避了main 为您真正希望由main 中调用的函数设置的关键字提供默认值的问题。如果您确实希望传递给mainNone 关键字值表示“将此关键字传递给其他函数时使用默认值”,则可以在kwargs_for_func 中设置omit_none 选项。

    def main(x, **kwargs):
        f1(x, **kwargs_for_func(f1, kwargs, True))
        f2(x, **kwargs_for_func(f2, kwargs, True))    
    
    main(1, key1=None)
    

    输出:

    (1, 42, None)
    

    注意事项

    这种方法的灵感来自this 其他关于 SO 的问题。 @BrenBarn 的问题和 cmets 帮助确定了问题所在,非常感谢!我会暂时搁置这个问题,看看其他人可能有什么不同的方法。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2010-09-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-12-06
      • 2016-12-13
      相关资源
      最近更新 更多