在Python的内置类型中,complex复数类型虽然不如int和float常用,但它在信号处理、图像旋转、电路分析、分形绘图等场景中有着不可替代的作用。多数主流语言并不直接内置复数,往往需要引入第三方库;Python则把复数作为一等公民,支持字面量、构造器以及完整的cmath数学函数库。这篇文章从复数的创建、运算、函数支持到典型代码实践逐步展开,帮助开发者在需要时能直接写出可运行的复数处理逻辑。
一、Python中复数的创建方式
复数由实部与虚部构成,形如a + bj。Python沿用了工程领域的惯例,用j而不是数学中的i表示虚数单位,因为i在电路分析中常代表电流。创建复数有两种主要方式。
第一种是字面量方式,虚部系数必须紧贴j。- z1 = 3 + 4j # 实部3 虚部4
- z2 = 1 - 2j
- z3 = 5j # 纯虚数 相当于0+5j
- z4 = 2 + 0j # 实数为2 但类型是complex
- z5 = 1 + 1j # 正确写法
- # z5 = 1 + j # 错误 这里的j会被当作变量名 导致NameError
复制代码 第二种是使用complex()函数,可以接收一个或两个数值参数,也可以接收字符串参数。- complex(5) # (5+0j)
- complex(3.14) # (3.14+0j)
- complex(3, 4) # (3+4j)
- complex(-1, 2.5) # (-1+2.5j)
- complex('3+4j') # (3+4j)
- complex('-1-2j') # (-1-2j)
- complex('5j') # 5j
- complex('3') # (3+0j)
复制代码 注意,complex()解析字符串时不允许出现空格,例如complex('3 + 4j')会抛出异常;字符串中也不能使用i代替j。
访问实部和虚部以及取共轭可以使用对象的属性与方法:- z = 3 + 4j
- print(z.real) # 3.0 返回float
- print(z.imag) # 4.0 返回float
- print(z.conjugate()) # (3-4j)
复制代码
二、复数运算与比较限制
复数的加减乘除、幂运算与普通数值类型一致,可以直接使用运算符。以a = 3 + 4j,b = 1 - 2j为例:- a = 3 + 4j
- b = 1 - 2j
- print(a + b) # (4+2j)
- print(a - b) # (2+6j)
- print(a * b) # (11-2j)
- print(a / b) # (-1+2j)
- print(a ** 2) # (-7+24j)
复制代码 乘法结果的推算过程:3×1 + 3×(-2j) + 4j×1 + 4j×(-2j) = 3 - 6j + 4j - 8j²,因j² = -1,所以最后是11 - 2j。除法可通过共轭分母化简得到结果。幂运算也遵守同样的复数运算法则。
复数只能判断相等或不等,不能比较大小。这在数学上也有意义,因为复数不是有序域。下面代码会触发TypeError:- a = 3 + 4j
- b = 3 + 4j
- print(a == b) # True
- print(a != b) # False
- # print(a > b) # TypeError: '>' not supported between instances of 'complex' and 'complex'
复制代码
内置函数abs()可用于求复数的模,即sqrt(实部² + 虚部²)。round()不能直接用于复数对象。
三、cmath模块:面向复数的数学函数
Python的math模块面向实数,复数运算需要使用cmath模块。cmath提供复变函数、极坐标转换以及判断函数。- import cmath
- z = 3 + 4j
- print(abs(z)) # 5.0 模长
- print(cmath.sqrt(z)) # (2+1j) 因为(2+j)²=3+4j
- print(cmath.polar(z)) # (5.0, 0.9272952180016122) 返回(模, 辐角)
- print(cmath.rect(5.0, 0.9272952180016122)) # (3+4j) 极坐标转直角坐标
复制代码 cmath.exp支持欧拉公式形式的指数运算,例如e^(jx) = cos(x) + j*sin(x):- x = 0.5
- print(cmath.exp(1j * x)) # (0.87758+0.47943j)
复制代码 cmath模块还包括log、log10、三角函数、反三角函数和双曲函数:- z = 1 + 1j
- cmath.exp(z)
- cmath.log(z) # 自然对数
- cmath.log10(z)
- cmath.sin(z)
- cmath.cos(z)
- cmath.tan(z)
- cmath.asin(z)
- cmath.acos(z)
- cmath.atan(z)
- cmath.sinh(z)
- cmath.cosh(z)
- cmath.tanh(z)
复制代码 判断与比较方面的工具:- cmath.isinf(complex(float('inf'), 0)) # True
- cmath.isnan(complex(float('nan'), 0)) # True
- cmath.isclose(1+2j, 1+2.0000000001j) # True 容差比较
复制代码
四、复数的实际应用代码实践
信号处理中DFT核心计算会使用复数指数。手写离散傅里叶变换有助于理解频谱形成的原理。下面代码直接定义DFT函数,并分析由50Hz和120Hz正弦波叠加的信号:- import cmath
- import math
- def discrete_fourier_transform(samples):
- n = len(samples)
- result = []
- for k in range(n):
- frequency_component = 0j
- for t in range(n):
- angle = -2 * cmath.pi * 1j * k * t / n
- frequency_component += samples[t] * cmath.exp(angle)
- result.append(frequency_component)
- return result
- sample_rate = 500
- duration = 1.0
- n_samples = int(sample_rate * duration)
- samples = []
- for i in range(n_samples):
- t = i / sample_rate
- value = math.sin(2 * math.pi * 50 * t) + 0.5 * math.sin(2 * math.pi * 120 * t)
- samples.append(value)
- frequency_spectrum = discrete_fourier_transform(samples)
- print('频率分析结果:')
- for i in range(10):
- magnitude = abs(frequency_spectrum[i])
- frequency = i * sample_rate / n_samples
- print(f'{frequency:.0f}Hz: 幅度={magnitude:.2f}')
复制代码 运行后能从频谱幅度中明显看到50Hz与120Hz两个峰值对应的频率分量。
图像处理或二维坐标系变换中,可以用复数表示平面上的点,实部为x坐标,虚部为y坐标。矩阵旋转因此可以简化为复数乘法:- import math
- def rotate_point(x, y, angle_degrees):
- point = complex(x, y)
- angle_radians = math.radians(angle_degrees)
- rotation = complex(math.cos(angle_radians), math.sin(angle_radians))
- rotated = point * rotation
- return rotated.real, rotated.imag
- print(rotate_point(1, 0, 90)) # (0.0, 1.0)
复制代码 同理,缩放点可以直接乘以常数因子。
电路分析中的交流阻抗也用复数表示:电阻为实数,电感和电容为纯虚数。下面实现RLC串联总阻抗的计算:- import cmath
- def impedance_resistor(r):
- return complex(r, 0)
- def impedance_inductor(inductance, frequency):
- omega = 2 * 3.14159 * frequency
- return complex(0, omega * inductance)
- def impedance_capacitor(capacitance, frequency):
- omega = 2 * 3.14159 * frequency
- return complex(0, -1 / (omega * capacitance))
- def series_impedance(*impedances):
- return sum(impedances)
- R = 100.0
- L = 0.1
- C = 10e-6
- f = 1000.0
- Z_total = series_impedance(
- impedance_resistor(R),
- impedance_inductor(L, f),
- impedance_capacitor(C, f)
- )
- print(f'总阻抗:{Z_total}')
- print(f'阻抗模:{abs(Z_total):.2f}欧姆')
- print(f'相位角:{cmath.phase(Z_total):.2f}弧度')
复制代码 这段代码验证了复数运算在电子仿真中的直接性。
曼德勃罗集是复数迭代的经典示例。用字符串字符着色可以快速在终端生成ASCII图案:- def mandelbrot(c, max_iterations=100):
- z = 0j
- for n in range(max_iterations):
- z = z * z + c
- if abs(z) > 2:
- return n
- return max_iterations
- def generate_mandelbrot(width=100, height=40, max_iter=50):
- x_min, x_max = -2.0, 1.0
- y_min, y_max = -1.0, 1.0
- for y in range(height):
- line = ''
- for x in range(width):
- real = x_min + (x / width) * (x_max - x_min)
- imag = y_min + (y / height) * (y_max - y_min)
- c = complex(real, imag)
- m = mandelbrot(c, max_iter)
- if m == max_iter:
- line += '#'
- elif m > max_iter * 0.8:
- line += '*'
- elif m > max_iter * 0.5:
- line += '+'
- elif m > max_iter * 0.2:
- line += '.'
- else:
- line += ' '
- print(line)
- generate_mandelbrot()
复制代码 执行后终端会出现经典的曼德勃罗集轮廓。
五、类型转换与易错点
复数不能直接转换为int或float,需要先取绝对值或分别取实部与虚部:- z = 3 + 4j
- str(z) # '(3+4j)'
- abs(z) # 5.0 float类型
- # int(z) # TypeError
- # float(z) # TypeError
复制代码 其他数值类型或字符串可以转换为复数,complex(5)、(3.14)或complex('1+2j')都会得到complex对象。
两个常见坑需要关注。第一个坑是求负数的平方根时,math.sqrt会直接报ValueError,必须改用cmath.sqrt,但cmath.sqrt返回的永远是complex类型,即使结果恰为实数:- import cmath
- import math
- result = cmath.sqrt(-1) # 1j
- print(cmath.sqrt(4)) # (2+0j)
复制代码 如果希望正数返回float、负数返回complex,可以封装一个安全开方函数:- def safe_sqrt(x):
- if x >= 0:
- return math.sqrt(x)
- else:
- return cmath.sqrt(x)
- print(safe_sqrt(4)) # 2.0
- print(safe_sqrt(-4)) # 2j
复制代码 第二个坑是复数的实部和虚部在Python内部总是float类型。即使写成3 + 4j,访问z.real得到的是3.0而不是整数3。这也意味着complex对象可以承载inf和nan等特殊浮点值:- z = 3 + 4j
- print(type(z.real)) # <class 'float'>
- print(type(z.imag)) # <class 'float'>
- z1 = complex(float('inf'), 0)
- z2 = complex(0, float('nan'))
复制代码
总结:Python内置complex使复数运算免去额外安装科学计算库的成本。创建上字面量最简洁、complex()最灵活;运算上支持四则与幂,但不能比较大小;函数支持依赖cmath模块;实际场景中DFT、图像旋转、交流阻抗与曼德勃罗集都能直接用原生代码落地。写代码时重点注意j的位置、字符解析空格和cmath返回类型的特性,就能避免多数报错。 |