NumPy标量与自定义序列、继承自列表的自定义类相乘表现差异的原因探究
这个问题的核心在于Python运算符重载的执行顺序,以及NumPy对不同类型对象的处理逻辑差异,我来给你拆解清楚:
首先,当你执行c * s这种乘法操作时,Python会遵循这样的流程:
- 先尝试调用左边对象(这里是
numpy.float64标量)的__mul__方法,看它能不能处理右边的对象; - 如果左边的
__mul__返回NotImplemented(表示它搞不定),才会转而调用右边对象的__rmul__方法,把乘法的控制权交给右边的对象。
接下来看两种情况的具体差异:
1. 自定义序列类(不继承list)
你的第一个S类完全是自定义的,只实现了__len__和__getitem__来模拟序列行为,没有继承任何Python内置序列类型,所以它没有默认的__rmul__方法。
当NumPy标量的__mul__方法遇到这个对象时,会判断它是一个可迭代的自定义序列(通过__len__和__getitem__识别),于是自动把它转换成NumPy数组,然后执行标量与数组的逐元素乘法,最终返回结果数组,这就是你得到array([3., 6., 9.])的原因。
2. 继承自list的自定义类
第二个S类继承了list,这意味着它继承了list的所有内置方法,包括__rmul__。
NumPy在处理与Python内置类型(及其子类)的交互时,会优先尊重内置类型的运算符逻辑。当它看到右边是list的子类时,会让__mul__返回NotImplemented,把控制权交给右边对象的__rmul__方法。而list的__rmul__方法的逻辑是重复序列(比如[1,2] * 3会生成[1,2,1,2,1,2]),但它只接受整数作为乘数。你的c是numpy.float64类型的3.0,不是整数,自然就触发了错误:can't multiply sequence by non-int of type 'numpy.float64'。
如果你想让继承自list的类也支持这种乘法
可以重写__rmul__方法,手动实现和NumPy标量的交互逻辑,比如:
import numpy as np class S(list): def __init__(self, lst): super().__init__(lst) # 这里要用super初始化父类list,不然继承的list实例是空的 def __rmul__(self, scalar): # 把自身转换成NumPy数组后再和标量相乘 return np.array(self) * scalar c = np.arange(3.0).sum() s = S([1, 2, 3]) print(c * s) # 输出:[3. 6. 9.]
备注:内容来源于stack exchange,提问作者Pavlo Bilous

