如何避免重复代码注册Pandas Series/DataFrame访问器?
为Pandas Series/DataFrame批量添加访问器方法并保留原函数签名
问题背景
需要通过Pandas的register_series_accessor和register_dataframe_accessor为Series和DataFrame批量绑定自定义方法,避免手写重复的访问器样板代码。现有实现可完成功能,但生成的方法签名为通用的*args, **kwargs,无法匹配原函数的参数信息;同时DataFrame版本需要在原函数参数基础上额外添加axis关键字参数(默认值0)。
核心实现思路
- 使用
functools.wraps保留原函数的元数据(文档字符串、名称等) - 借助
inspect模块解析原函数签名,动态调整包装函数的签名:- Series场景:移除原函数第一个参数(对应传入的Series对象)
- DataFrame场景:移除原函数第一个参数后,追加
axis关键字参数
完整代码实现
1. 定义核心操作函数
import pandas as pd import inspect from functools import wraps def mf_cumsum_plus_const(ser, const=0): """对Series做累加后加上常数 参数: const: 要添加的常数,默认0 返回: 处理后的Series """ return ser.cumsum().add(const) def mf_cumprod_plus_const(ser, const=0): """对Series做累乘后加上常数 参数: const: 要添加的常数,默认0 返回: 处理后的Series """ return ser.cumprod().add(const) MYFUNC_LIST = [mf_cumsum_plus_const, mf_cumprod_plus_const]
2. Series访问器实现
@pd.api.extensions.register_series_accessor('mf') class MFSeriesAccessor: def __init__(self, pandas_obj): self._obj = pandas_obj for func in MYFUNC_LIST: self.tack_on_function(func) def tack_on_function(self, func): # 提取方法名(去掉前缀mf_) new_name = '_'.join(func.__name__.split('_')[1:]) @wraps(func) def inner(*args, **kwargs): # 绑定当前Series对象到原函数的第一个参数 return func(self._obj, *args, **kwargs) # 调整签名:移除原函数的第一个参数(ser) sig = inspect.signature(func) params = list(sig.parameters.values())[1:] inner.__signature__ = sig.replace(parameters=params) inner.__name__ = new_name setattr(self, new_name, inner)
3. DataFrame访问器实现
@pd.api.extensions.register_dataframe_accessor('mf') class MFDataFrameAccessor: def __init__(self, pandas_obj): self._obj = pandas_obj for func in MYFUNC_LIST: self.tack_on_function(func) def tack_on_function(self, func): # 提取方法名(去掉前缀mf_) new_name = '_'.join(func.__name__.split('_')[1:]) @wraps(func) def inner(*args, axis=0, **kwargs): # 对DataFrame的指定轴应用原函数 return self._obj.apply(func, axis=axis, args=args, kwargs=kwargs) # 调整签名:移除原函数第一个参数,追加axis关键字参数 sig = inspect.signature(func) params = list(sig.parameters.values())[1:] # 添加axis参数,仅允许关键字传递,默认值0 axis_param = inspect.Parameter( 'axis', inspect.Parameter.KEYWORD_ONLY, default=0 ) params.append(axis_param) inner.__signature__ = sig.replace(parameters=params) inner.__name__ = new_name setattr(self, new_name, inner)
验证效果
# 测试Series访问器 ser = pd.Series([1, 2, 3]) print("Series测试结果:") print(ser.mf.cumsum_plus_const(const=5)) # 查看方法签名(会显示正确的参数信息) print("\nSeries方法签名:") help(ser.mf.cumsum_plus_const) # 测试DataFrame访问器 df = pd.DataFrame({'a': [1, 2, 3], 'b': [4, 5, 6]}) print("\nDataFrame测试结果:") print(df.mf.cumsum_plus_const(axis=0, const=5)) # 查看方法签名(包含axis参数) print("\nDataFrame方法签名:") help(df.mf.cumsum_plus_const)
运行后,IDE的代码补全和help()都会显示正确的参数列表,而非通用的*args, **kwargs。
内容的提问来源于stack exchange,提问作者8one6
相关产品推荐
相关产品推荐

