2013-05-07 14 views
15

インスタンス属性がその値を変更するたびに別の関数を呼び出すため、__setattr__のPythonクラスのメソッドをオーバーライドしようとしています。これまでのところ、私は、実行時に__setattr__を上書きせずに、このソリューションを持っている実行時に__setattr__を無効にする

この初期化時に、私は、後に使用されようとしているいくつかの属性を設定しているのでしかし、私は、 __init__方法でこの動作をしたくありません私はで試してみました

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) 

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__を上書きしたいと思いますおよびobject.__setitem__['__setitem__'] = self.auxがありますが、これらのアテンプのどれも効果がありません。私はthis section of the data model referenceを読んだが、それは自分自身の割り当てのように見える__setattr__は少しトリッキーです。

どう__init__の終わりに__setattr__を上書きすることは可能かもしれない、あるいは少なくとも__setattr__のみコンストラクタで通常の方法で呼び出された神託のソリューションがありますか?

答えて

20

残念ながら、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回upvoteできません:p – velis

3

SingleNegationEliminationの答えは素晴らしいですが、それは子クラスの__mro__店のオリジナル以来、継承して動作することはできません@スーパークラスのクラス。

考え方は簡単で、__setattr____init__の前にスイッチし、__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 
関連する問題