python 高效和/或可读的方法来扁平化嵌套的**kwargs,同时保留键值对?

zf9nrax1  于 2023-05-21  发布在  Python
关注(0)|答案(3)|浏览(77)

说明

我试图创建一个帮助函数,它多次调用另一个函数。对于helper函数,我希望将变量作为**kwargs传入,以便允许main函数确定每个参数的默认值。
传入的参数可以是可变长度的可迭代对象,并将被连接到多个字典中。下面是一个输入和解析表单的示例:

{'param1': ['arg1'], 'param2': ['arg1', 'arg2', 'arg3'], 'param3': ['arg1', 'arg2']}

#=>

[{'param1': 'arg1', 'param2': 'arg1', 'param3': 'arg1'}, {'param2': 'arg2', 'param3': 'arg2'}, {'param2': 'arg3'}]

python有没有内置的函数可以让你用这种方式扁平化一个字典?我想保留键值对,因为它们将在调用main函数时用作关键字参数。

我尝试了:

首先,我试图避免将**kwargs传递到主函数,方法是将参数转换为列表,然后将它们传递到itertools.zip_longest()

for data, param1, param2, param3 in itertools.zip_longest(external_data, argv1, argv2, argv3):
  foo(data, param1, param2, param3) # Invoke main function

但是,这会强制使用None或其他填充值,并隐藏main函数定义的默认值。
其次,我使用了嵌套列表解析来解析**kwargs,并创建了一个类似于我上面描述的字典列表。

foo = [{k: v[idx]
       for k, v in kwargs.items() if idx < len(v) and v[idx] is not escape}
       for idx in range(len(longest_argument_list))]

然而,这迫使我在解析**kwargs之前迭代所有的kwargs.values()以获得最长参数列表的长度。

我在寻找什么

理想情况下,有一种更简单的方法可以使用内置函数将**kwargs扁平化到多个字典中。如果没有,可能有一个内置的比嵌套列表解析方法性能更好的方法。
允许某种形式的标记值来表示需要跳过特定函数调用的参数(例如:传入param1=['arg1', None, 'arg3']以允许main的第二次调用使用param1的默认值)。

展示预期行为的脚本:

import collections
import inspect

def invoked_function(param, param1=None, param2='', param3='.'):
  """This function only prints its own call, but it would
   perform some actions using param and **kwargs"""
  variables = inspect.currentframe().f_locals
  function = inspect.currentframe().f_code.co_name
  output = f'{function}(**{variables})'
  print(output)

def helper_function(**kwargs):
  external_data = ['target1', 'target2', 'target3', 'target4']
  longest_argument_list = max(kwargs.values(), key=len)
  escape = None
  foo = [{k: v[idx]
         for k, v in kwargs.items() if idx < len(v) and v[idx] is not escape}
         for idx in range(len(longest_argument_list))]
  foo = collections.deque(foo)
  for target in external_data:
    kwargs = foo.popleft() if foo else {}
    invoked_function(target, **kwargs)

if __name__ == '__main__':
  helper_function(param1=['arg1'],
                  param2=['arg1', 'arg2', 'arg3'],
                  param3=['arg1', 'arg2'])

上面的脚本按原样工作。

628mspwn

628mspwn1#

我希望我没理解错你的问题。如果你想用可变参数调用invoked_function(),你可以使用自定义sentinel对象的zip_longest

from itertools import zip_longest

def invoked_function(param, param1=None, param2="", param3="."):
    print(param, param1, param2, param3)

def helper_function(**kwargs):
    sentinel = object()
    for t1 in zip_longest(*kwargs.values(), fillvalue=sentinel):
        invoked_function(
            "some_target",
            **{
                param: value
                for param, value in zip(kwargs, t1)
                if not value is sentinel
            }
        )

dct = {
    "param1": ["arg1"],
    "param2": ["arg1", "arg2", "arg3"],
    "param3": ["arg1", "arg2"],
}

helper_function(**dct)

图纸:

some_target arg1 arg1 arg1
some_target None arg2 arg2
some_target None arg3 .
svmlkihl

svmlkihl2#

您可以使用此命令展平结构:

from itertools import zip_longest

kwargs = {'param1': ['arg1'], 'param2': ['arg1', 'arg2', 'arg3'], 'param3': ['arg1', 'arg2']}
flattened_kwargs = []

for arg in zip_longest(*kwargs.values(), fillvalue='None'):
    flattened_kwargs.append({key: val for key, val in zip(kwargs.keys(), arg) if val is not 'None'})
    
print(flattened_kwargs)

输出:

[{'param1': 'arg1', 'param2': 'arg1', 'param3': 'arg1'}, {'param2': 'arg2', 'param3': 'arg2'}, {'param2': 'arg3'}]

我使用了'None',因为您也希望使用默认值,但您可以使用任何“unaccepted”参数代替它。

h7appiyu

h7appiyu3#

在对这个问题进行了更多的思考之后,我意识到可以通过将平坦化函数与辅助函数分离来改进原始程序结构。
使用Andrej Kesely's Answer作为如何使用zip_longest的灵感,我想出了这个解决方案:

def generate_flattened_kwargs(**kwargs):
  keyword_argument_mappings = map(zip,
                                  itertools.cycle([kwargs]),
                                  itertools.zip_longest(*kwargs.values()))
  for keyword_arguments in list(keyword_argument_mappings):
    flat_kwargs = dict(keyword_arguments)
    yield flat_kwargs

在非生成器形式中(用于timeit测试):

def flatten_kwargs(**kwargs):
  keyword_argument_mappings = map(zip,
                                  itertools.cycle([kwargs]),
                                  itertools.zip_longest(*kwargs.values()))
  flattened_kwargs = []
  for keyword_arguments in list(keyword_argument_mappings):
    flat_kwargs = dict(keyword_arguments)
    flattened_kwargs.append(flat_kwargs)
  return flattened_kwargs

最明显的缺点是,itertools.zip_longest集合fillvalue的kwargs不会被过滤掉。
作为交换,这种实现比dict表达式构造更快。list()dict()是用C实现的,而dict和list表达式是用Python字节码实现的。
以下是我使用timeit模块在系统上获得的一些数字:

Timing: helper_func_from_question_example_script(**dct) for 10000 trials, with 100 repetitions each.
Total time: 1.8271372999370215
Mean time: 0.00018271372999370213   Median time: 0.00018010000349022448
Worst time: 0.0026543999993009493   Best time: 0.0001736999984132126
Timing: flatten_kwargs(**dct) for 10000 trials, with 100 repetitions each.
Total time: 1.4693315000549774
Mean time: 0.00014693315000549774   Median time: 0.00014650000230176374
Worst time: 0.000338400001055561    Best time: 0.00013989999570185319
Timing: helper_func_from_Andrej(**dct) for 10000 trials, with 100 repetitions each.
Total time: 1.5763073998314212
Mean time: 0.0001576307399831421    Median time: 0.00015359999815700576
Worst time: 0.0004923000014969148   Best time: 0.00014769999688724056

Process finished with exit code 0

实际上,这种差异非常小,除非您处理大量数据,否则不会产生太大的影响。我最终使用了生成器实现,因为它在我的项目中带来了更好的代码可重用性。

相关问题