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

如何实现适配多种函数类型的参数修改型Python装饰器?及最佳实践咨询

问题描述

我希望制作一个可用于任意方法或函数的装饰器,同时修改传入目标函数的参数。以下是示例代码:

import math

def chunk(l, n):
    for i in range(0, len(l), n):
        yield l[i:i + n]

def divide_and_run(func):
    def wrapper(self, entries, *args, **kwargs):
        output = []
        for c in chunk(entries, 3):
            output.append(func(self, c, *args, **kwargs))
        return output
    return wrapper

class Dummy:
    @divide_and_run
    def multiply_all(self, mylist):
        return math.prod(mylist)

a = [1,2,3,4,5,6,7,8,9,10]
d = Dummy()

print(d.multiply_all(a))

当前的divide_and_run装饰器会分割mylist,在每个chunk上执行目标函数并返回输出列表,但它无法用于无self参数的自由函数。

我有两个需求:

  1. 修改传入目标函数的参数(将mylist替换为chunk);
  2. 让该装饰器可适配任意方法或函数类型(如实例方法、自由函数)。

请问最佳实践是什么?修改参数时使用装饰器是否合适?


解决方案与最佳实践

一、适配任意函数/方法的装饰器实现

关键要分清装饰的是实例方法(带self)还是自由函数(不带self),没必要硬写死参数位置,用inspect模块能优雅处理各种情况,同时让装饰器支持配置要分割的参数位置或名称,灵活性拉满。

改进版:按参数名称指定分割目标(推荐)

这个版本不用管参数顺序,直接指定要分割的参数名,不管是实例方法还是自由函数都能兼容:

import math
from inspect import signature

def chunk(l, n):
    for i in range(0, len(l), n):
        yield l[i:i + n]

def divide_and_run(chunk_size=3, arg_name=None):
    def decorator(func):
        def wrapper(*args, **kwargs):
            # 把传入的参数绑定成字典,统一处理
            sig = signature(func)
            bound_args = sig.bind(*args, **kwargs).arguments
            
            # 检查目标参数是否存在
            if not arg_name or arg_name not in bound_args:
                raise ValueError(f"得指定目标函数里要分割的参数名,比如@divide_and_run(arg_name='mylist')")
            target_list = bound_args[arg_name]
            
            output = []
            for c in chunk(target_list, chunk_size):
                # 复制参数并替换成chunk
                new_args = bound_args.copy()
                new_args[arg_name] = c
                # 转成函数需要的参数格式调用
                output.append(func(*[new_args[param] for param in sig.parameters]))
            return output
        return wrapper
    return decorator

测试实例方法和自由函数

1. 实例方法用起来:

class Dummy:
    @divide_and_run(arg_name="mylist")
    def multiply_all(self, mylist):
        return math.prod(mylist)

d = Dummy()
a = [1,2,3,4,5,6,7,8,9,10]
print(d.multiply_all(a))  # 输出: [6, 120, 504, 90]

2. 自由函数直接套:

@divide_and_run(arg_name="nums")
def multiply_free(nums):
    return math.prod(nums)

print(multiply_free(a))  # 输出一样:[6, 120, 504, 90]

备选:按参数位置指定

如果习惯用参数位置,也可以写个版本,自动识别实例方法(给参数索引+1跳过self):

from inspect import ismethod

def divide_and_run(chunk_size=3, arg_index=0):
    def decorator(func):
        def wrapper(*args, **kwargs):
            # 是实例方法的话,第一个参数是self,要分割的参数索引+1
            target_idx = arg_index + 1 if ismethod(args[0]) else arg_index
            target_list = args[target_idx]
            
            output = []
            for c in chunk(target_list, chunk_size):
                new_args = list(args)
                new_args[target_idx] = c
                output.append(func(*new_args, **kwargs))
            return output
        return wrapper
    return decorator

这个版本用的时候,实例方法要注意索引是self之后的位置,比如@divide_and_run(arg_index=0)对应实例方法的第二个参数(self是第一个)。

二、装饰器改参数合适吗?

完全合适!装饰器本来就是用来包装函数调用的,改输入参数、增强输出、加日志/缓存这些都是它的本职工作。只要做到这几点就没问题:

  • 行为透明:别人用这个装饰器的时候,一眼能知道它会把指定参数分割成块来执行
  • 职责单一:别让装饰器同时干分割、日志、缓存好几件事,就专注“分块执行+收集结果”
  • 灵活配置:别硬写死chunk大小和参数名,做成可配置的,方便复用

最佳实践总结

  • 优先选按参数名称的实现,比按位置靠谱多了,函数参数顺序改了也不影响
  • 用inspect模块处理参数绑定,不用自己瞎判断self,兼容所有函数类型
  • 把装饰器做成带参数的(比如传chunk_size、arg_name),别写死配置
  • 加个参数校验,比如检查指定的参数名是否存在,避免运行时才报错

内容的提问来源于stack exchange,提问作者user987654

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.23 13:54:25