Python动态添加只读property问题:实例设置为何返回property对象?
问题原因
property是类属性,只有定义在类层面时,Python的属性访问机制才会触发它的getter/setter逻辑。当你通过self.__setattr__给实例设置属性时,这个property对象只是作为普通的实例属性被存储,访问时直接返回对象本身,不会触发getter。而notMyName是定义在类上的property,所以访问时会正常调用getter。
解决方案
方案一:动态修改类属性
既然property需要在类层面生效,那就在实例初始化时,直接给类添加这个property属性,而不是给实例加。这样所有实例都会共享这个类级别的property,访问时就会触发getter:
def property_factory(name): def getter(self): return self.__getattribute__(name) def setter(self, value): raise Exception('Cannot set a value') return property(getter, setter) class read_file(object): def __init__(self): self.name = 'myName' self.value = 'myValue' class a(object): def __init__(self): list1 = read_file() # 先设置私有实例属性 self.__setattr__('_' + list1.name, list1.value) # 动态给类添加property setattr(a, list1.name, property_factory('_' + list1.name)) # 测试 b = a() print(b.myName) # 输出 'myValue' try: b.myName = 'true' except Exception as e: print(e) # 输出 'Cannot set a value'
注意:如果多个实例初始化时会添加同名的类属性,后面的会覆盖前面的,适用于每个实例添加的属性名唯一的场景。
方案二:重写__getattr__和__setattr__
如果不想修改类属性,可以重写类的__getattr__和__setattr__方法,自己管理只读属性的映射:
def property_factory(name): def getter(self): return self.__getattribute__(name) def setter(self, value): raise Exception('Cannot set a value') return property(getter, setter) class read_file(object): def __init__(self): self.name = 'myName' self.value = 'myValue' class a(object): def __init__(self): self._read_only_attrs = {} # 存储只读属性名和对应的私有属性名 list1 = read_file() private_name = '_' + list1.name self.__setattr__(private_name, list1.value) self._read_only_attrs[list1.name] = private_name def __getattr__(self, name): # 当属性不存在时触发,检查是否是只读属性 if name in self._read_only_attrs: private_name = self._read_only_attrs[name] return self.__getattribute__(private_name) raise AttributeError(f"'a' object has no attribute '{name}'") def __setattr__(self, name, value): # 检查是否是只读属性,如果是则抛出异常 if hasattr(self, '_read_only_attrs') and name in self._read_only_attrs: raise Exception('Cannot set a value') # 其他属性正常设置 super().__setattr__(name, value) # 测试 b = a() print(b.myName) # 输出 'myValue' try: b.myName = 'true' except Exception as e: print(e) # 输出 'Cannot set a value'
这种方法不会修改类属性,每个实例维护自己的只读属性列表,更灵活,适合每个实例有不同只读属性的场景。
补充说明
- 方案一中,如果你不想影响其他实例,可以考虑创建类的子类,在子类上添加
property后实例化子类,但这样会增加类的数量,按需选择即可。 - 方案二中,
__getattr__只有在访问不存在的实例属性时才会触发,所以如果有同名的类属性,会优先访问类属性,这点需要注意。
内容的提问来源于stack exchange,提问作者Mezzerine
相关产品推荐
相关产品推荐

