2013-05-07 68 views
15

我想覆盖Python类的__setattr__方法,因为每次实例属性更改其值时都要调用另一个函数。不过,我不希望在__init__方法这种行为,因为这在初始化过程中我设置一些属性,其将要在以后使用:到目前为止,我有这个解决方案在运行时覆盖__setattr__

,而无需在运行覆盖__setattr__

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) # Call to a function using self.b 
     object.__setattr__(self, name, value) 

然而,我想避免这些object.__setattr__(...)并在__init__方法结束时覆盖__setattr__

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) 
     # override self.__setattr__ here 

    def aux(self, name, value): 
     print(self.b) 
     object.__setattr__(self, name, value) 

我与试图和object.__setitem__['__setitem__'] = self.aux,但这些尝试都没有效果。我已阅读this section of the data model reference,但它看起来像自己__setattr__的任务有点棘手。

怎么可能在__init__的末尾覆盖__setattr__,或者至少有一个pythonic解决方案,其中__setattr__只在构造函数中以正常方式调用?

回答

20

不幸的是,没有办法“在init之后重写”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 
+1

哇!哇哇!我偶然发现了这一点,几乎将它解雇了,但事实证明,这正是我需要解决类似问题的方法。这个答案中有一些严重的Python魔法。对不起,我无法注册100次:p – velis 2014-08-07 08:09:46

3

@ SingleNegationElimination的答案是伟大的,但它不能与传承工作,因为孩子上课的__mro__店的原超类课程。通过他的回答启发,变化不大,

的想法很简单,__init__之前切换__setattr__,并且__init__结束后,其恢复。

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): 
     print 'after __init__', self.b 
     super(Foo, self).__setattr__(key, value) 


class Bar(Foo): 
    def __init__(self): 
     super(Bar, self).__init__() 
     self.c = 3 

>>> f = Foo() 
>>> f.a = 10 
after __init__ 2 
>>> 
>>> b = Bar() 
>>> b.c = 30 
after __init__ 2