在运行时覆盖 __setattr__
在运行时覆盖 __setattr__
我正在尝试覆盖Python类的__setattr__
方法,因为我想在每次实例属性更改值时调用另一个函数。然而,在__init__
方法中,我不想要这种行为,因为在初始化期间我设置了一些稍后要使用的属性:\n到目前为止,我有这个解决方案,没有在运行时覆盖__setattr__
:\n
class Foo(object): def __init__(self, a, host): object.__setattr__(self, 'a', a) object.__setattr__(self, 'b', b) result = self.process(a) for key, value in result.items(): object.__setattr__(self, key, value) def __setattr__(self, name, value): print(self.b) # 使用self.b调用函数 object.__setattr__(self, name, value)
\n然而,我想避免这些object.__setattr__(...)
,并在__init__
方法的末尾覆盖__setattr__
:\n
class Foo(object): def __init__(self, a, b): self.a = a self.b = b result = self.process(a) for key, value in result.items(): setattr(self, key, value) # 在此处覆盖self.__setattr__ def aux(self, name, value): print(self.b) object.__setattr__(self, name, value)
\n我尝试了self.__dict__[\'__setitem__\'] = self.aux
和object.__setitem__[\'__setitem__\'] = self.aux
,但是这些尝试都没有效果。我阅读了数据模型参考手册的这一部分,但似乎分配自己的__setattr__
有点棘手。\n如何在__init__
的末尾覆盖__setattr__
,或者至少有一个Python风格的解决方案,在构造函数中只调用普通方式的__setattr__
?
问题的出现原因:
上述代码中,问题出现在子类Bar的初始化方法__init__中。在调用super(Bar, self).__init__()时,会执行父类Foo的__init__方法。而在Foo的__init__方法中,调用了父类object的__init__方法,并在之后设置了self.a和self.b的值。然而,在设置self.a和self.b的值时,触发了父类Foo的__setattr__方法。在这个方法中,打印了self.b的值。然而,由于子类Bar并没有设置self.b的值,所以打印的结果是2,而不是期望的None。
解决方法:
为了解决上述问题,可以通过重写__setattr__方法来实现。在重写的__setattr__方法中,首先调用super(Foo, self).__setattr__(key, value)来执行父类Foo的__setattr__方法,然后再打印self.b的值。
完整代码如下:
class CleanSetAttrMeta(type): def __call__(cls, *args, **kwargs): real_setattr = cls.__setattr__ cls.__setattr__ = object.__setattr__ self = super(CleanSetAttrMeta, cls).__call__(*args, **kwargs) cls.__setattr__ = real_setattr return self class Foo(object): __metaclass__ = CleanSetAttrMeta def __init__(self): super(Foo, self).__init__() self.a = 1 self.b = 2 def __setattr__(self, key, value): super(Foo, self).__setattr__(key, value) print 'after __init__', self.b class Bar(Foo): def __init__(self): super(Bar, self).__init__() self.c = 3 f = Foo() f.a = 10 b = Bar() b.c = 30
运行上述代码,输出结果如下:
after __init__ 2 after __init__ 2
通过重写__setattr__方法后,解决了子类Bar中调用父类Foo的__init__方法时,打印self.b的值不正确的问题。
在Python中,很遗憾没有办法在初始化之后"覆盖"特殊方法,这是由于查找的副作用所导致的。问题的关键在于,在开始查找特殊方法之前,Python实际上并不查看实例本身,只是获取其类,因此无法通过对象的状态来影响查找哪个方法。
如果不喜欢在__init__
中的特殊行为,可以重构代码,将特殊知识放在__setattr__
中。例如:
class Foo(object): __initialized = False def __init__(self, a, b): try: self.a = a self.b = b # ... finally: self.__initialized = True def __setattr__(self, attr, value): if self.__initialzed: print(self.b) super(Foo, self).__setattr__(attr, value)
编辑:实际上,有一种方法可以改变查找哪个特殊方法,只要在初始化之后改变它的类。这种方法会让你深入到元类的细节中,所以没有进一步的解释,下面是具体实现:
class AssignableSetattr(type): def __new__(mcls, name, bases, attrs): def __setattr__(self, attr, value): object.__setattr__(self, attr, value) init_attrs = dict(attrs) init_attrs['__setattr__'] = __setattr__ init_cls = super(AssignableSetattr, mcls).__new__(mcls, name, bases, init_attrs) real_cls = super(AssignableSetattr, mcls).__new__(mcls, name, (init_cls,), attrs) init_cls.__real_cls = real_cls return init_cls def __call__(cls, *args, **kwargs): self = super(AssignableSetattr, cls).__call__(*args, **kwargs) print "Created", self real_cls = cls.__real_cls self.__class__ = real_cls return self class Foo(object): __metaclass__ = AssignableSetattr def __init__(self, a, b): self.a = a self.b = b for key, value in process(a).items(): setattr(self, key, value) def __setattr__(self, attr, value): frob(self.b) super(Foo, self).__setattr__(attr, value) def process(a): print "processing" return {'c': 3 * a} def frob(x): print "frobbing", x myfoo = Foo(1, 2) myfoo.d = myfoo.c + 1
哇!真是太棒了。我偶然发现了这个方法,差点忽视了它,但事实证明它正是我解决类似问题所需的。这个答案中有一些真正的Python魔法。很抱歉我不能点赞100次 :p