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

Python 将关键字参数传递给关键字具有默认值的另一个函数,python,Python,下面代码的目的是将传递给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

下面代码的目的是将传递给
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)
(1, 42, None)
(1, None, None)
(1, 42, 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)

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

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

def main(x, key1=None, key2=None):
    if some.otherCondition():
      key1 = "Blah"
    f2(x, key1=key1, key2=key2)
为了实现您所描述的,Python必须检查
key1
的每一次使用,以查看它是否恰好等于默认值。如果您使用
key1
执行任何操作,除了直接将其传递给另一个函数之外,这很容易变得非常混乱

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

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

def read_delimited_file(filename, separator="\t"):
    # .,..
这里,
read\u csv
的全部要点是覆盖它调用的函数中的默认分隔符,同时仍然允许用户在调用
read\u 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()
,在某种程度上实现自动化,但对于几个参数来说,显式地这样做更为明显。)

必须这样做可能看起来很“愚蠢”,但其实并不愚蠢,因为默认行为应该是这样的并不明显。

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

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

可能的解决办法 一种选择是使用内省来定义一个函数,该函数将
main
中的
kwargs
子集为
main
调用的每个函数的适当dict。这避免了将特定于每个函数的逻辑写入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, None)
(1, None, None)
(1, 42, None)
(1, None, None)
(1, 42, None)
由于
main
正在收集
kwarg
dict,这就避免了
main
为确实需要由
main
中调用的函数设置的关键字提供默认值的问题。如果您确实希望传递给
main
None
关键字值表示“将此关键字传递给其他函数时使用默认值”,则可以在
kwargs\u for_func
中设置
ommit\u 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, None)
(1, None, None)
(1, 42, None)
(1, None, None)
(1, 42, None)
笔记
这种方法受到了其他问题的启发@BrenBarn的问题和评论有助于确定问题所在,谢谢!我将把这个问题留一段时间,看看其他人可能有什么不同的方法

在您设想的方案中,如果用户调用
main(1,None)
(为
key1
显式传递None),会发生什么情况?我不明白您为什么要将默认值放在f1和f2中。由于main提供了默认值,这些变量将始终具有值。@SBHayes:因为这些函数可能会在其他位置调用,而不仅仅是从
main
@BrenBarn:问得好。根据它的实现方式,可能存在一些模糊性。老实说,在这种情况下,无论行为是什么,拥有一个能起作用的东西都是很好的。但是我不希望任何人在
main
中显式地将值设置为
None
,因为函数本身是一个包装器。