2013-05-07 79 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