如何处理多个关键字参数?

时间:2018-07-16 13:38:19

标签: python function arguments

在传递具有相同键的两个关键字参数时,在定义用户友好的函数界面时遇到了一些麻烦。

问题

调用具有两个关键字参数具有相同键并且第二关键字参数具有优先级的函数的最佳方法是什么?
如果发生此问题,则第一个关键字参数始终来自dict中未压缩的数据库,而第二个关键字参数始终通过“直接”作为关键字参数来传递。 br /> 在功能的外部,数据库字典值不得覆盖,因为它们可以多次使用。
编辑:为了保持该功能对用户的可用性,首选后端实现。这意味着用户可以简单地将参数传递给函数,而无需使用其他模块,而函数本身可以发挥所有魔力。


问题

我这里有一个名为fun_one的函数,该函数接收程序用户直接定义的大量参数。例如,这可以是热交换器的lengthwidth。为了简化该函数的使用并使调用代码尽可能短,鼓励使用数据库。这些数据库包含dict(或大熊猫系列)中的数据,在本例中为inputs
要将数据库dict inputs传递给该函数,请用**inputs将其解压缩,然后作为关键字参数传递。
现在,如果用户要覆盖数据库的特定参数,我对用户友好方法的理解将是让他再次传递前面的参数,例如使用length=23.7,并在内部覆盖数据库中的参数。但是,当然(请参见示例代码)这会在我什至无法进入try/except的函数之前引发错误:

  

TypeError:fun_one()为关键字参数“ length”获得了多个值

代码示例再现错误

def fun_one(*args, **kwargs):  # short example function
    print(kwargs)

inputs = {'length': 15.8, 'width': 1.1, 'some_other_args': np.random.rand(3)}

fun_one(**inputs, length=23.7)

我当前的解决方案

我当前的解决方案fun_two涉及不解压缩数据库并将其传递给*args。它检查*args中是否有dict个,并将尚未在kwargs中的值设置为kwargs ,如下面的代码示例所示。

def fun_two(*args, **kwargs):  # example function printing kwargs
    print(kwargs)  # print kwargs before applying changes
    for arg in args:  # find dicts
        if type(arg) is dict:
            for key, val in arg.items():  # loop over dict
                _ = kwargs.setdefault(key, val)  # set val if key not in dict
    print(kwargs)  # print kwargs after applying changes

inputs = {'length': 15.8, 'width': 1.1, 'some_other_args': np.random.rand(3)}

fun_two(inputs, length=23.7)

但是这种方法对用户而言是非常晦涩的,并且需要在很多功能的开始处进行循环和检查,因为这将适用于众多功能。 (我将其外包给模块,因此每个函数一行一行。但是它仍然偏离我对简单明了的函数定义的理解。)

有没有更好的方法(更Pythonic的)来做到这一点?在调用函数的过程中,我是否监督过某些方法?性能没关系。
预先感谢!

2 个答案:

答案 0 :(得分:2)

最简单的解决方案是使用ChainMapmanual pages)中的collections。这样,您可以选择优先使用哪些参数。示例:

from collections import ChainMap

def fun_one(*args, **kwargs):  # short example function
    print(kwargs)

inputs = {'length': 15.8, 'width': 1.1, 'some_other_args': 1}

c = ChainMap({'length': 23.7}, inputs)  # we overwrite length here
fun_one(**c)

输出:

{'some_other_args': 1, 'width': 1.1, 'length': 23.7}

如果仅通过输入调用fun_one:

c = ChainMap(inputs)
# or c = inputs
fun_one(**c)

输出将是:

{'width': 1.1, 'length': 15.8, 'some_other_args': 1}

摘自手册:

  

ChainMap 将多个字典或其他映射分组在一起以创建   单个可更新的视图。如果未指定地图,则为一个空白   提供字典,以便新链始终至少包含一个   映射。

您可以将此ChainMap包装在装饰器中,其中一项更改是,不使用fun_one()调用**input,仅调用input

from collections import ChainMap

def function_with_many_arguments(func):
    orig_func = func
    def _f(*args, **kwargs):
        if args:
            c = ChainMap(kwargs, args[0])
            return orig_func(**c)
        else:
            return orig_func(*args, **kwargs)
    return _f

@function_with_many_arguments
def fun_one(*args, **kwargs):  # short example function
    print(kwargs)

inputs = {'length': 15.8, 'width': 1.1, 'some_other_args': 1}
fun_one(inputs, length=23)

打印:

{'some_other_args': 1, 'length': 23, 'width': 1.1}

答案 1 :(得分:1)

作为对Andrej Kesely答案的扩展(再次感谢!),我添加了ChainMap循环,以允许在同一函数中使用多个数据库,并能够使用各种位置参数。多个数据库的优先级是先到先服务,但是在这种情况下可以。这是装饰器:

def function_with_many_arguments(func):
    orig_func = func
    def _f(*args, **kwargs):
        if args:
            c = ChainMap(kwargs)
            for arg in args:
                if type(arg) is dict:
                    c = ChainMap(c, arg)
            orig_func(*args, **c)
        else:
            orig_func(*args, **kwargs)
    return _f

这是我的扩展示例函数,其中包含一些代码对其进行测试。我只是添加了各种随机参数,没有考虑使用任何Pythonic方式...;)

@function_with_many_arguments
def fun_one(a, b, *args, name, database=None, **kwargs):
    print(name)
    print(a, b)
    print(kwargs)

inputs = {'length': 15.8, 'width': 1.1, 'some_other_args': np.random.rand(3)}
inputs2 = inputs.copy()
inputs2['width'] = 123
inputs2['weight'] = 3.8

fun_one(4, 8, inputs, database=inputs2, name='abc', length=23.8, weight=55)