Python中简单赋值是否会对变量进行两次求值?
验证Python简单赋值与增强赋值的求值次数差异
要验证普通赋值(如x = x + 1)中x被求值两次,核心是让x的求值过程带有可观测的副作用,且x需是复杂表达式(如属性引用、下标操作)——简单局部变量的求值无明显副作用,无法直接观测次数差异。以下是两种有效验证方法:
方法1:带副作用的下标键调用
利用下标操作的键为带计数副作用的函数,直观展示求值次数:
count = 0 def get_key(): global count count += 1 print(f"get_key 被调用,次数: {count}") return 'a' d = {'a': 1} # 测试增强赋值 print("=== 增强赋值 d[get_key()] += 1 ===") d[get_key()] += 1 print(f"增强赋值后计数: {count}") # 输出1,仅调用一次get_key # 重置计数,测试普通赋值 count = 0 print("\n=== 普通赋值 d[get_key()] = d[get_key()] + 1 ===") d[get_key()] = d[get_key()] + 1 print(f"普通赋值后计数: {count}") # 输出2,调用两次get_key
原理
增强赋值中,d[get_key()]这个表达式仅被求值一次:既用来获取当前值,也用来确定赋值目标;而普通赋值中,右边的d[get_key()]是一次求值(获取值),左边的d[get_key()]是另一次求值(确定赋值位置),因此触发两次函数调用。
方法2:带副作用的属性访问拦截
自定义类,通过__getattribute__拦截所有属性访问(包括赋值前的目标查找),统计访问次数:
class TestObj: def __init__(self): self._x = 1 self.access_count = 0 def __getattribute__(self, name): # 拦截x属性的所有访问(读取、赋值前查找) if name == 'x': self.access_count += 1 print(f"访问属性x,次数: {self.access_count}") return super().__getattribute__(name) def __setattr__(self, name, value): # 避免内部属性赋值触发计数混乱 if name in ('_x', 'access_count'): super().__setattr__(name, value) else: # 赋值x时会先触发__getattribute__查找目标 super().__setattr__(name, value) # 测试增强赋值 print("=== 增强赋值 obj.x += 1 ===") obj1 = TestObj() obj1.x += 1 print(f"增强赋值后访问次数: {obj1.access_count}") # 输出1 # 测试普通赋值 print("\n=== 普通赋值 obj.x = obj.x + 1 ===") obj2 = TestObj() obj2.x = obj2.x + 1 print(f"普通赋值后访问次数: {obj2.access_count}") # 输出2
原理
__getattribute__会在每次访问x时计数:
- 普通赋值中,右边读取
obj.x触发一次计数,左边查找obj.x作为赋值目标再触发一次计数; - 增强赋值中,
obj.x仅被访问一次,同时完成值的获取和赋值目标的确定。
为什么你之前的测试未达预期?
- 简单局部变量:局部变量的求值只是栈帧中的加载/存储操作,无明显副作用,无法观测两次求值的区别;
- 仅统计读取副作用:若用
property的getter仅统计值的读取,普通赋值只会触发一次getter(右边读取),左边赋值不会触发getter,导致计数与增强赋值一致,无法体现差异。
内容的提问来源于stack exchange,提问作者An old man in the sea.
相关产品推荐
相关产品推荐

