为什么 complex 与 np.float64 相加返回 complex 而非 np.complex128?
结论
在题述行为对应的 CPython 环境中,np.float64 继承 Python 内置的 float,所以左侧 complex 的加法实现可以直接接受它,并返回内置 complex。运算由 Python 完成,没有交给 NumPy 做类型提升。
NumPy 的类型提升图描述了它如何选择结果的 dtype。预测混合标量表达式时,还要先确认实际执行运算的是哪一方。
为什么 np.float64 会被 Python 直接处理
NumPy 标量的继承关系并不完全相同:np.float64 继承 Python float,而 np.float16、np.float32 不继承它;NumPy 整数类型也不继承 Python int。可以用 issubclass() 检查这些关系。NumPy 标量文档
import numpy as np
print(issubclass(np.float64, float)) # True
print(issubclass(np.float32, float)) # False
print(issubclass(np.float16, float)) # False
print(issubclass(np.int64, int)) # False
以 CPython 3.13 的实现为例,complex 加法接受内置复数、整数、浮点数及其子类。PyFloat_Check() 能识别 float 子类;计算完成后,PyComplex_FromCComplex() 创建内置 complex 对象。CPython 3.13 源码
题中表达式的运算过程可以这样理解:
- 左操作数是内置
complex,Python 先尝试它的加法实现。 - 右操作数
np.float64(1.0)也是float实例,可以被该实现接受。 - 加法返回内置
complex,无需尝试 NumPy 的反向加法。
由于 np.float64 不是 complex 的子类,也不会触发右侧子类反向运算优先的规则。Python 对 __add__()、__radd__() 和 NotImplemented 的分派规则见数据模型文档。
为什么其他几组运算返回 NumPy 标量
对于 np.float16、np.float32 和题中的 NumPy 整数,内置 complex 加法无法通过上述内置类型路径直接处理,因而返回 NotImplemented。Python 随后尝试右操作数的反向加法,由 NumPy 接手计算。
运算进入 NumPy 后,才会应用相应的 dtype 提升规则。按照 NumPy 2.x 对 Python 标量的规则,Python complex 与 np.float16 或 np.float32 混合时可得到 np.complex64;与 NumPy 整数混合时则得到 np.complex128。这与题中的观察一致。NumPy 类型提升文档
这些表达式的运算分派路径不同。仅凭类型提升图,无法判断所有混合标量表达式最终返回的 Python 对象类型。
如何检查当前环境中的行为
原问题没有提供 Python 和 NumPy 版本。复现时应记录版本,并用 type() 检查,避免仅凭显示的 (2+1j) 判断类型:
import sys
import numpy as np
z = complex(1+1j)
x = np.float64(1.0)
print(sys.version)
print(np.__version__)
result = z + x
print(repr(result))
print(type(result))
print(type(result) is complex)
print(type(result) is np.complex128)
# 直接观察内置 complex 加法是否接受这些操作数。
print(complex.__add__(z, x))
print(complex.__add__(z, np.float32(1.0)))
在题述分派路径下,最后两行分别返回复数结果和 NotImplemented。直接调用这些特殊方法适合诊断,业务计算使用普通运算符或 NumPy API 即可。
如何明确获得 np.complex128
可以直接调用 NumPy 的 np.add(),并指定计算 dtype:
import numpy as np
z = complex(1+1j)
x = np.float64(1.0)
result = np.add(z, x, dtype=np.complex128)
print(result)
print(type(result)) # <class 'numpy.complex128'>
np.add() 是 NumPy 的 ufunc,支持 dtype 参数;两个输入都是标量时返回标量。numpy.add 文档
也可以先把复数显式转换为 NumPy 标量:
result = np.complex128(z) + x
若只想查看 NumPy 按提升规则选择的 dtype,可以使用:
print(np.result_type(z, x)) # complex128
np.result_type() 计算的是 NumPy 的提升结果,不会模拟 Python + 的完整运算符分派,因此不能保证它的结果与 type(z + x) 对应。numpy.result_type 文档
备注:内容仅供参考。
