2013-08-08 7 views
60

クラスがサブクラス化されているときにコードをトリガする方法はありますか?Python:クラスがサブクラス化されたときにコードを実行する

class SuperClass: 
    def triggered_routine(subclass): 
     print("was subclassed by " + subclass.__name__) 

magically_register_triggered_routine() 

print("foo") 

class SubClass0(SuperClass): 
    pass 

print("bar") 

class SubClass1(SuperClass): 
    print("test") 

は出力

foo 
was subclassed by SubClass0 
bar 
test 
was subclassed by SubClass1 
+1

メタクラスを使用します。メタクラスは、インスタンスの作成時にクラスが呼び出されるのと同じように、クラスが作成されるときに呼び出されます。 –

+2

は答えを追加することはできませんが、今日のpython3.6は '__init_subclass__'を持っている - それをチェックアウト! –

+1

@OrDuan:ありがとう、役に立つ音。私の問題のために "メタクラスを使う"の代わりに専用の解決策があるので、この質問の重複を解消するのに十分な理由があるかもしれません。 –

答えて

39

クラス(デフォルトでは)typeのインスタンスであるべきです。 クラスFooのインスタンスがfoo = Foo(...)によって作成されたのと同様に、type(つまりクラス)のインスタンスがmyclass = type(name, bases, clsdict)によって作成されます。

クラス作成の時点で特別なことが起こりたい場合は、クラスを作成するものを変更する必要があります。つまり、typeです。これを行う方法は、typeのサブクラス、つまりメタクラスを定義することです。

メタクラスは、そのインスタンスがクラスであるため、クラスに属します。

はPython2では、Watchertypeのサブクラスである

class SuperClass: 
    __metaclass__ = Watcher 

でクラスのメタクラスを定義します。

のpython3で構文はどちらも、この場合には、nameが文字列'Superclass'に等しく、basesタプル(object,)ある

Superclass = Watcher(name, bases, clsdict) 

に相当する

class SuperClass(metaclass=Watcher) 

に変更されました。 clsdictは、クラス定義の本体で定義されたクラス属性の辞書です。

myclass = type(name, bases, clsdict)に類似していることに注意してください。

だから、あなたは、インスタンスの作成の瞬間にイベントを制御するために、クラスの__init__を使用するのと同じように、あなたはメタクラスの__init__で、クラスの創造の瞬間にイベントを制御することができます


class Watcher(type): 
    def __init__(cls, name, bases, clsdict): 
     if len(cls.mro()) > 2: 
      print("was subclassed by " + name) 
     super(Watcher, cls).__init__(name, bases, clsdict) 

class SuperClass: 
    __metaclass__ = Watcher 


print("foo") 

class SubClass0(SuperClass): 
    pass 

print("bar") 

class SubClass1(SuperClass): 
    print("test") 

プリント

foo 
was subclassed by SubClass0 
bar 
test 
was subclassed by SubClass1 
+2

ありがとうございます。しかし、あなたのコードはpython2でしか動かない。 python3では、スーパークラスが 'クラススーパークラス(メタクラス=ウォッチャー)する必要があります: –

+0

感謝をpass'。私はPython2とPython3の構文の違いに対処するために投稿を編集しました。私は自分自身でこれをしようとしたとき、私は逃した – unutbu

+1

2つの重要なもの:1 - 'Watcher'サブクラス' type'が、ない 'object'。 2 - 'SuperClass'には' object'ではなくスーパークラスが全くありません。私は、私が定義したすべてのクラスのスーパークラスとして 'object'を書き留めることに慣れているので、何とかこのコードが何か他のものを使用していたことが間違っていました。 SuperClass'がWatcher'は 'type'が、ない' object'から継承する 'ためobject' ...それは間違いなく必要だ' 'を継承することで任意の害があるように思えませんが。 – ArtOfWarfare

6

編集:私の古い投稿は実際には機能しませんでした。 classmethodからサブクラス化すると、期待どおりに機能しません。

最初に、この特定のメソッドがサブクラスの動作で特別な呼び出しを行うと想定されていることをメタクラスに伝えるために、呼び出す関数に属性を設定するだけです。便宜上、関数をclassmethodに変更して、それが見つかった実際のベースクラスも発見できるようにします。私たちはクラスメソッドを返すので、最も便利なデコレータとして使用できます。

import types 
import inspect 

def subclass_hook(func): 
    func.is_subclass_hook = True 
    return classmethod(func) 

またsubclass_hookデコレータを使用したことを確認するための便利な方法をするつもりです。私たちは、classmethodが使用されていることを知っているので、我々はそれをチェックし、だけにしてis_subclass_hook属性について見ていきます。

def test_subclass_hook(thing): 
    x = (isinstance(thing, types.MethodType) and 
     getattr(thing.im_func, 'is_subclass_hook', False)) 
    return x 

は最後に、私たちは情報に作用するメタクラスが必要になります。ほとんどの場合、ここで行うには、最も興味深いのは、ちょうどフックのための供給拠点のそれぞれを確認しています。そうしたやり方で、スーパーは少なくとも驚くべき方法で動作します。

class MyMetaclass(type): 
    def __init__(cls, name, bases, attrs): 
     super(MyMetaclass, cls).__init__(name, bases, attrs) 

     for base in bases: 
      if base is object: 
       continue 
      for name, hook in inspect.getmembers(base, test_subclass_hook): 
       hook(cls) 

とする必要があります。

>>> class SuperClass: 
...  __metaclass__ = MyMetaclass 
...  @subclass_hook 
...  def triggered_routine(cls, subclass): 
...   print(cls.__name__ + " was subclassed by " + subclass.__name__) 

>>> class SubClass0(SuperClass): 
...  pass 
SuperClass was subclassed by SubClass0 

>>> class SubClass1(SuperClass): 
...  print("test") 
test 
SuperClass was subclassed by SubClass1 
関連する問題