You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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()仅能拿到函数返回值的字符串表示,无法得到函数调用本身的表示。
简洁实现方案

核心逻辑两点:

  1. 所有参数值统一用repr()处理,自动适配字符串、数字、列表、字典、布尔值、None等所有类型的合法Python字面量表示,不需要手动判断字符串类型加引号,避免类型判断遗漏。
  2. 位置参数、关键字参数分别生成对应的字符串片段,统一收集到列表中,最后用", ".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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 16:48:44