将关键字参数传递给关键字具有默认值的另一个函数

时间:2015-07-16 01:05:46

标签: python

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

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)

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

2 个答案:

答案 0 :(得分: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()聪明地自动化这一点,但是对于只有几个参数明确地做它更明确。)

看起来好像"愚蠢"必须这样做,但它并不是很愚蠢,因为这不是默认行为应该是什么。

答案 1 :(得分:0)

原始代码中的问题

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

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

可能的解决方案

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

此更改允许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提供您真正希望由{{1}中调用的函数设置的关键字的默认值}。如果您确实希望传递给main的{​​{1}}关键字值表示“将此关键字传递给其他函数时使用默认值”,则可以在{{1}中设置None选项}}

main

输出:

omit_none

注释

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