Python中如何简洁实现**kwargs解包的反向操作
问题描述
我近期正在学习Python装饰器,为练习装饰器用法,正在编写一个日志装饰器,用于记录函数的调用时机、传入函数的*args和**kwargs参数,以及函数调用的__repr__结果,以便后续可以重新执行对应调用。
在Python中,对象的__repr__()方法会返回一个字符串,部分场景下可将该字符串传入eval()来重建对象。我正尝试为函数调用实现类似形式的__repr__。
由于装饰器会包装任意函数,无法预先获知被包装函数的参数定义,因此需要借助*args和**kwargs来构造__repr__字符串,目标格式如下:
"function_name(positional_arg1, positional_arg2, keyword_arg1='some_value', keyword_arg2='other_value')"
核心需求:从**kwargs重构参数列表,也就是将格式为{'kwarg1': val1, 'kwarg2': val2}的字典,转换为kwarg1=val1, kwarg2=val2格式的参数序列。
说明:将
*args解包为参数列表不存在障碍,只需去掉args元组的外层括号,元组内容就可以直接作为位置参数传入函数。例如args元组('arg1', 'arg2')可直接转换为'arg1', 'arg2',因此问题核心聚焦于kwargs字典转换回关键字参数列表的实现方式。
现有实现功能可用但写法冗余,需要更简洁的实现方式,移除装饰器相关辅助代码后核心逻辑如下:
def print_args(*args, **kwargs): # 生成kwargs的字符串表示 kwargs_repr = '' # 未传入kwargs时,kwargs为空字典dict() if kwargs != dict(): num_kwargs = len(kwargs) # 转换为参数列表要求的格式(即key1=val1,而非{key1: val1}) for n, (kw, val) in enumerate(kwargs.items()): kwargs_repr += str(kw) + '=' # 若值为字符串,需要额外添加引号,保证传入eval()后仍为字符串类型 if type(val) == str: kwargs_repr += '"' + val + '"' else: kwargs_repr += str(val) # 参数间添加逗号分隔,最后一个参数后不添加 if n < num_kwargs - 1: kwargs_repr += ', ' call_repr = ( "print_args(" # str(args)[1:-1]用于移除元组外层的括号 f"{str(args)[1:-1] if args != tuple() else ''}" f"{', ' if args != tuple() and kwargs != dict() else ''}" f"{kwargs_repr})" ) print(call_repr) return call_repr returned_repr = print_args('pos_arg1', 'pos_arg2', start=0, stop=10, step=1)
运行输出:
print_args('pos_arg1', 'pos_arg2', start=0, stop=10, step=1)
已尝试的无效方案
- 已知可以直接通过
any_func(*args, **kwargs)的方式传参调用,也可以通过字符串替换的粗暴方式拼接eval语句,但希望遵循eval(repr(obj))的标准范式,避免杂乱的字符串替换操作。 - 尝试过列表/元组推导式生成键值对字符串,但推导式会生成独立字符串的元组,例如
('key1=val1', 'key2=val2'),无法直接得到拼接完成的连续参数序列,不能被eval识别为合法关键字参数。 - 直接使用
repr(func(*args, **kwargs))不可行:该写法会先执行函数,repr()仅能拿到函数返回值的字符串表示,无法得到函数调用本身的表示。
简洁实现方案
核心逻辑两点:
- 所有参数值统一用
repr()处理,自动适配字符串、数字、列表、字典、布尔值、None等所有类型的合法Python字面量表示,不需要手动判断字符串类型加引号,避免类型判断遗漏。 - 位置参数、关键字参数分别生成对应的字符串片段,统一收集到列表中,最后用
", ".join()一次性拼接,完全不需要手动处理逗号分隔、空值判断的冗余逻辑。
优化后的代码:
def print_args(*args, **kwargs): # 收集所有参数片段 args_parts = [repr(arg) for arg in args] kwargs_parts = [f"{k}={repr(v)}" for k, v in kwargs.items()] all_parts = args_parts + kwargs_parts # 拼接为最终调用字符串 call_repr = f"print_args({', '.join(all_parts)})" print(call_repr) return call_repr # 测试(兼容嵌套容器等复杂参数类型) returned_repr = print_args('pos_arg1', 'pos_arg2', start=0, stop=10, step=1, tags=['a', 'b'], debug=False)
运行输出:
print_args('pos_arg1', 'pos_arg2', start=0, stop=10, step=1, tags=['a', 'b'], debug=False)
该实现生成的字符串可以直接传入eval()执行,代码量压缩到原有实现的1/3,没有冗余的循环计数、分支判断逻辑,适配所有合法Python参数类型。
内容的提问来源于stack exchange,提问作者ryparker
相关产品推荐
相关产品推荐

