子类化Pandas Series重载运算符触发递归错误的排查请求
子类化Pandas Series并重载运算符触发递归错误的解决思路
问题复现
最小复现代码
import pandas as pd class MySeries(pd.Series): def __mul__(self, other): print('hello!') return super().mul(other) MySeries([1,2,3]) * 1
运行错误信息
hello! ... hello! File "/home/jibi/.local/lib/python3.9/site-packages/pandas/core/ops/__init__.py", line 197, in flex_wrapper return op(self, other) File "<stdin>", line 4, in __mul__ ... File "/home/jibi/.local/lib/python3.9/site-packages/pandas/core/ops/__init__.py", line 197, in flex_wrapper return op(self, other) File "<stdin>", line 4, in __mul__ packages/pandas/core/ops/common.py", line 90, in get_op_result_name if isinstance(right, (ABCSeries, ABCIndex)): RecursionError: maximum recursion depth exceeded
观察到的现象
- 添加
mul方法无改善 - 添加
__rmul__方法无改善 - 添加
_constructor方法无改善(遵循官方子类化文档操作) - 仅与整数相乘时出错,与Series或子类实例相乘正常
- 直接调用
MySeries([1,2,3]).mul(1)也报错,移除__mul__方法则正常 - 该问题同样出现在
__add__、__sub__等其他运算符上 - 子类化DataFrame并以相同方式重载
__mul__时,*运算符可正常工作(仅Series子类存在此问题)
使用版本:pandas 1.4.3;python 3.9.5
解决思路
问题根源是Pandas的运算符分发机制:重载__mul__后,调用super().mul(other)时,Pandas内部的flex_wrapper会再次触发自定义的__mul__方法,形成递归循环。
可行解决方案
方案一:直接调用父类的__mul__方法
修改重载逻辑,跳过Pandas的mul包装方法,直接调用父类的运算符实现:
import pandas as pd class MySeries(pd.Series): def __mul__(self, other): print('hello!') return super().__mul__(other) MySeries([1,2,3]) * 1 # 正常运行,输出hello!并返回正确结果
方案二:使用底层算术方法避免递归
调用Pandas内部的_arith_method直接执行运算,绕开运算符包装的递归触发:
import pandas as pd import operator from pandas.core.ops import _arith_method class MySeries(pd.Series): def __mul__(self, other): print('hello!') return _arith_method(self, other, operator.mul, 'mul')
方案三:结合构造器确保返回子类实例
正确实现_constructor方法,让运算结果返回MySeries实例,同时避免递归:
import pandas as pd class MySeries(pd.Series): def _constructor(self, values, index=None, dtype=None, name=None, fastpath=False): return MySeries(values, index=index, dtype=dtype, name=name) def __mul__(self, other): print('hello!') result = super().__mul__(other) return self._constructor(result)
核心原因:Pandas的mul实例方法会通过内部的运算符分发逻辑再次调用自定义的__mul__,而直接调用super().__mul__会跳过这个包装流程,执行原始的Series乘法运算,从而终止递归循环。
内容的提问来源于stack exchange,提问作者user2655730
相关产品推荐
相关产品推荐

