Python使用__getattr__与__setattr__实现子变量双访问方式及报错解决
问题根因
- 递归触发原因:
你自定义的__setattr__拦截了所有实例属性赋值操作,初始化self.children = {}时,属性名children没有被你的放行规则匹配到,不会被真正赋值到实例上。后续访问self.children时,因为实例本身没有这个属性,会触发__getattr__,而__getattr__中又返回self.children,再次触发__getattr__,形成死循环最终抛出递归溢出错误。 __setattr__逻辑错误:
你当前写的super().__setattr__('children[{}]'.format(prop), val)是给实例新增了一个名为children[Alfa]的字符串属性,完全没有操作children字典内的元素,无法实现预期的赋值逻辑。__getattr__冗余逻辑:__getattr__中判断name == 'children'的分支完全没必要,只要children被正确赋值到实例上,访问该属性不会触发__getattr__。
修正方案
完整可运行代码:
#!/usr/bin/env python3.6 class Child(object): def __init__(self, name:str, value = 0): self.name = name self.value = value def __repr__(self): return ' child "{}" has value {}\n'.format(self.name, self.value) class Parent(object): def __init__(self, name:str): self.name = name self.children = {} def __repr__(self): s = 'Parent "{}":\n'.format(self.name) for k, v in self.children.items(): s += v.__repr__() return s def __getattr__(self, name:str): # 只有实例不存在的属性才会走到这里,无需额外判断children if name in self.children: return self.children[name].value raise AttributeError(f"'Parent' object has no attribute '{name}'") def __setattr__(self, prop, val): # 放行实例原生属性name和children,直接赋值到实例 if prop in ('name', 'children'): super().__setattr__(prop, val) else: # 子项已存在则修改对应value if prop in self.children: self.children[prop].value = val # 可选逻辑:子项不存在则自动新增Child实例到children字典,不需要可删除改为抛异常 else: self.children[prop] = Child(prop, val) p = Parent('Tango') p.children['Alfa'] = Child('Alfa', 55) p.children['Bravo'] = Child('Bravo', 66) print(p) print(p.children['Alfa']) # 输出 child "Alfa" has value 55 print(p.Alfa) # 输出 55 print('-----') p.Alfa = 99 print(p.Alfa) # 输出 99 print('-----') print(p.children['Alfa']) # 输出 child "Alfa" has value 99 print(p) # 输出的Alfa value已更新为99
效果说明
修正后p.Alfa = 99会直接修改children['Alfa']的value属性,完全符合需求。可选的自动新增子项逻辑可以根据实际业务需求调整。
内容的提问来源于stack exchange,提问作者Lance E.T. Compte
相关产品推荐
相关产品推荐

