【问题标题】:How to deal with multiple keyword arguments?如何处理多个关键字参数?
【发布时间】:2018-07-16 13:38:18
【问题描述】:

我在处理定义用户友好的函数接口时遇到了一些麻烦当使用相同的键传递两个关键字参数时

问题

两个关键字参数具有相同键第二个关键字参数具有优先权的情况下,调用函数的最佳方法是什么?
如果出现此问题,第一个关键字参数始终来自 dict 中的解压缩数据库,而第二个关键字参数始终通过“直接”作为关键字参数传递。
数据库字典值不得在函数的外部副本中被覆盖,因为它们可能会被多次使用。
编辑:为了保持功能对用户的可用性,后端实现是首选。这意味着用户可以简单地将参数传递给函数,而无需使用额外的模块,而函数本身可以完成所有的魔法。


问题

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

TypeError: fun_one() 为关键字参数“长度”获取了多个值

重现错误的代码示例

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 中的dicts 并将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)的方法来做到这一点?在调用函数的过程中,我是否监督了一些方法?性能无关紧要。
提前致谢!

【问题讨论】:

    标签: python function arguments


    【解决方案1】:

    最简单的解决方案是使用来自collections (manual pages) 的ChainMap。这样你就可以选择哪些参数优先。示例:

    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 将多个 dicts 或其他映射组合在一起以创建 一个单一的、可更新的视图。如果未指定地图,则为单个空 提供字典以便一个新的链总是至少有一个 映射。

    你可以将这个 ChainMap 包装在装饰器中,一个变化是不要用**input 调用fun_one(),只调用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}
    

    【讨论】:

    • 感谢您的回答!我能否以某种方式在函数的 backend 上进行这项工作,以便用户不必直接使用ChainMap?如果没有,这至少会使arg.items() 上的循环过时。
    • @Scotty1- 我更新了我的答案 - 制作了一个装饰器来包装 ChainMap
    • 太棒了,这看起来是一个很棒的实现!为了实现所有类型的位置参数和多个数据库,我在args 上添加了一个循环来检查dict,如果存在多个,则将ChainMap 设为ChainMap。如果有人感兴趣,我可以将此作为次要答案发布。
    • @Scotty1- 是的,为什么不:)
    【解决方案2】:

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

    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)
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-01-26
      • 1970-01-01
      • 2010-10-09
      • 2020-02-15
      • 2017-07-14
      • 1970-01-01
      • 2021-03-19
      • 1970-01-01
      相关资源
      最近更新 更多