Python循环动态添加类property属性为何覆盖先前属性?
问题根因
该问题和property对象的内存空间、拷贝逻辑无关,核心原因是Python闭包的迟绑定(Late Binding)机制:
- 循环中定义的
lambda表达式引用了外层循环变量item,但Python不会在lambda定义时就固定当时的item值,只会在lambda实际被调用时,才向外层作用域查找item的当前值。 - 循环结束后,外层作用域的
item最终值为'z',因此所有属性的getter、setter被触发时,读到的都是最终值'z',才会出现所有属性行为一致的现象。
C.x == C.z返回False也很好解释:每次循环都会创建一个全新的独立property对象,三个属性绑定的是三个完全不同的property实例,自然相等判断、身份判断都返回False,只是这三个property内部存储的访问函数,都引用了同一个外层循环变量item而已。
这也是copy.deepcopy无效的原因:深拷贝只会复制property对象本身,不会修改lambda对外层变量的引用逻辑,拷贝后的property持有的lambda依然会在调用时查找外层最终的item值,问题没有任何改善。
正确实现方案
核心思路是在定义访问函数时,就把当前循环的item值固定住,避免迟绑定影响,常见实现有三种:
方案1:利用函数默认参数固定值
Python函数的默认参数是在函数定义阶段完成求值的,不会随外层变量变化,因此可以把当前item作为默认参数传入lambda,实现值绑定:class C(object): def _get_item(self, name): print(f"I'm getter of {name}") def _set_item(self, name, value): print(f"I'm setter of {name}") for item in ['x', 'y', 'z']: prop = property( fget=lambda self, cur_item=item: self._get_item(cur_item), fset=lambda self, x, cur_item=item: self._set_item(cur_item, x) ) setattr(C, item, prop)方案2:用工厂函数隔离作用域
编写独立的property生成函数,每次调用时都会生成独立的函数作用域,保存当前传入的属性名,完全和外层循环变量隔离:class C(object): def _get_item(self, name): print(f"I'm getter of {name}") def _set_item(self, name, value): print(f"I'm setter of {name}") def create_property(name): def fget(self): return self._get_item(name) def fset(self, value): self._set_item(name, value) return property(fget, fset) for item in ['x', 'y', 'z']: setattr(C, item, create_property(item))方案3:用
functools.partial提前绑定参数
通过partial提前把属性名作为固定参数传入访问函数,调用时无需再动态查找外层变量:from functools import partial class C(object): def _get_item(self, name): print(f"I'm getter of {name}") def _set_item(self, name, value): print(f"I'm setter of {name}") for item in ['x', 'y', 'z']: prop = property( fget=partial(lambda self, name: self._get_item(name), name=item), fset=partial(lambda self, val, name: self._set_item(name, val), name=item) ) setattr(C, item, prop)
以上任意一种实现运行后,各属性的getter、setter都会正确匹配对应的属性名,不会再出现全部指向z的问题。
内容的提问来源于stack exchange,提问作者Roman Kiselev
相关产品推荐
相关产品推荐

