Python中__mul__与__rmul__调用规则及列表与numpy数组相乘异常问题
现象背后的核心逻辑
你遇到的反常表现是Python为数值计算生态预留的兼容设计导致的,核心涉及两个规则:标准二元运算符的调用流程、内置类型对numpy数组协议的特殊处理。
完整的二元运算符调用规则(以*为例)
官方文档描述的规则是简化版,完整执行流程如下:
- 执行
a * b时优先调用a.__mul__(b)- 若方法存在且返回值不是
NotImplemented,直接使用该返回值 - 若方法不存在,或返回
NotImplemented,进入下一步
- 若方法存在且返回值不是
- 校验右操作数类型:
- 若
b的类型是a类型的子类,直接判定运算不支持,抛出TypeError - 若不是子类,尝试调用
b.__rmul__(a),若方法存在且不返回NotImplemented,使用该返回值
- 若
- 上述流程都失败则抛出
TypeError
你测试的反常现象原因
1. 原生list和numpy标量相乘的表现
原生list的__mul__是C语言实现的内置方法,做了特殊兼容处理:通过运算符触发调用时,会先检测右操作数是否实现了numpy的数组通用函数协议__array_ufunc__,如果是就直接返回NotImplemented,把运算优先级让给numpy的实现。
- 你用
a * b触发运算时,list.__mul__检测到b是numpy标量,直接返回NotImplemented,Python转而调用b.__rmul__(a),把list转成numpy数组做数值乘法,得到[2 4 6] - 你手动显式调用
a.__mul__(b)时,相当于直接调用list的乘法逻辑,跳过了协议检测的前置步骤,numpy的0维标量会被当成整数处理,所以返回重复两次的列表[1,2,3,1,2,3]
2. Foo1和Foo2的差异原因
Foo1继承自list,没有重写__mul__,用的还是C实现的原生内置方法,所以运算符触发时会走和原生list一样的协议检测逻辑,返回NotImplemented,调用numpy的__rmul__得到数组结果Foo2重写了__mul__,是Python层面实现的方法,没有内置的协议检测逻辑,所以运算符触发时直接执行你实现的乘法逻辑,把numpy标量当整数处理,返回重复后的列表
这个设计不需要numpy修改内置类型的行为,是Python内置类型本身预留了对应兼容接口,方便第三方数值库实现自定义的运算逻辑。
内容的提问来源于stack exchange,提问作者z.ni
相关产品推荐
相关产品推荐

