赋值运算符
赋值运算符是 Python 中将对象绑定到名字的核心机制。= 是最基础的赋值形式,而 +=、-=、*=、/=、//=、%=、**= 等复合赋值运算符则在修改变量时提供了更简洁的语法。此外,Python 的多重赋值和序列解包让一次为多个变量赋值成为可能。
基础赋值 =
= 将右侧表达式的结果绑定到左侧的名字。Python 的赋值不是"把值存入变量",而是"让名字引用对象"。
>>> width = 20
>>> height = 5 * 9
>>> width * height
900
如果变量未定义就使用,会触发 NameError:
>>> n
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'n' is not defined
多重赋值
Python 允许用一条语句为多个变量赋值。右侧表达式从左到右依次求值,然后同时绑定到左侧变量。这意味着可以在不使用临时变量的情况下交换两个值。
>>> a, b = 0, 1
>>> a
0
>>> b
1
# 交换变量
>>> a, b = b, a
>>> a
1
>>> b
0
多重赋值的右侧在赋值发生前完全求值,因此 a, b = b, a + b 这类表达式能正确工作:右侧的 a + b 使用的是赋值前的旧值。
>>> a, b = 0, 1
>>> a, b = b, a + b
>>> a, b
(1, 1) # a 获得旧 b(1),b 获得旧 a+旧 b(0+1)
这种特性在实现斐波那契数列时特别优雅:
>>> a, b = 0, 1
>>> while a < 10:
... print(a)
... a, b = b, a + b
...
0
1
1
2
3
5
8
链式赋值
链式赋值 a = b = c = 0 将同一个对象同时绑定到多个名字。注意这并不意味着创建多个独立副本——如果绑定的是可变对象,通过任一名字修改都会影响其他名字。
>>> a = b = []
>>> a.append(1)
>>> b
[1] # a 和 b 引用同一个列表
>>> x = y = 5
>>> x += 1
>>> x
6
>>> y
5 # 整数不可变,x 现在引用新对象 6,y 仍引用 5
复合赋值运算符
复合赋值运算符将运算和赋值合并,形式为 变量 运算符= 表达式,等价于 变量 = 变量 运算符 表达式。但两者并非完全等价,复合赋值对可变对象可能执行就地修改。
>>> salary = 10000
>>> salary += 2000 # 等价于 salary = salary + 2000
>>> salary
12000
>>> count = 100
>>> count //= 3 # count = count // 3
>>> count
33
>>> base = 2
>>> base **= 10 # base = base ** 10
>>> base
1024
对于不可变类型(数字、字符串、元组),a += b 与 a = a + b 效果相同,都是创建新对象。对于可变类型如列表,+= 调用 __iadd__ 方法执行就地扩展,比 a = a + b 更高效:
>>> a = [1, 2]
>>> id_before = id(a)
>>> a += [3, 4] # 就地扩展,a 的 id 不变
>>> id(a) == id_before
True
>>> b = [1, 2]
>>> id_before = id(b)
>>> b = b + [3, 4] # 创建新列表,b 的 id 改变
>>> id(b) == id_before
False
所有算术复合赋值运算符一览:
>>> x = 10
>>> x += 5; print(x) # 15
>>> x -= 3; print(x) # 12
>>> x *= 2; print(x) # 24
>>> x /= 4; print(x) # 6.0
>>> x //= 2; print(x) # 3.0
>>> x %= 2; print(x) # 1.0
>>> x **= 3; print(x) # 1.0
序列解包赋值
Python 支持将可迭代对象解包到多个变量。左侧的变量数量必须与右侧可迭代对象的元素数量匹配,否则会触发 ValueError。
>>> coordinates = (3, 4)
>>> x, y = coordinates
>>> x
3
>>> y
4
>>> data = [1, 2, 3]
>>> first, second, third = data
>>> first
1
解包不限于元组和列表,任何可迭代对象都可以:
>>> a, b, c = "xyz"
>>> a
'x'
>>> head, tail = [1, 2, 3, 4, 5]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: too many values to unpack (expected 2)
扩展解包(星号表达式)
Python 3 引入了星号表达式 *rest,可以捕获任意数量的剩余元素。一个解包表达式中只能有一个星号变量。
>>> first, *rest = [1, 2, 3, 4, 5]
>>> first
1
>>> rest
[2, 3, 4, 5]
>>> *beginning, last = [1, 2, 3, 4, 5]
>>> beginning
[1, 2, 3, 4]
>>> last
5
>>> first, *middle, last = [1, 2, 3, 4, 5]
>>> first, middle, last
(1, [2, 3, 4], 5)
星号变量总是得到列表,即使原始序列是元组或字符串:
>>> a, *b = (1, 2, 3)
>>> b
[2, 3] # 列表,不是元组
如果只给星号变量赋值,它会捕获所有元素:
>>> *all_items, = (1, 2, 3)
>>> all_items
[1, 2, 3] # 将元组转为列表的技巧
嵌套解包
解包可以嵌套,用于处理嵌套序列:
>>> data = ('Alice', (25, 'Engineer'))
>>> name, (age, job) = data
>>> name
'Alice'
>>> age
25
>>> job
'Engineer'
解包的应用场景
解包赋值让代码更简洁。遍历字典时,可以直接解包键值对:
>>> scores = {'Alice': 85, 'Bob': 92}
>>> for name, score in scores.items():
... print(f"{name}: {score}")
...
Alice: 85
Bob: 92
函数返回多个值时,调用方可以直接解包:
>>> def get_min_max(numbers):
... return min(numbers), max(numbers)
...
>>> minimum, maximum = get_min_max([3, 1, 4, 1, 5])
>>> minimum
1
>>> maximum
5
赋值运算符看似简单,但复合赋值的就地修改特性、多重赋值的求值顺序、扩展解包的灵活语法,都是提高代码效率和可读性的关键工具。理解这些机制的内部行为,有助于避免可变对象共享引用带来的意外副作用。