137

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

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
4

2 に答える 2

74

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

クラス作成の瞬間に何か特別なことをしたい場合は、クラスを作成するものを変更する必要があります-つまりtype. その方法は、サブクラス、typeつまりメタクラスを定義することです。

クラスがそのインスタンスに対するものであるように、メタクラスはそのクラスに対するものです。

Python2 では、クラスのメタクラスを次のように定義します。

class SuperClass:
    __metaclass__ = Watcher

Watcherのサブクラスですtype

Python3 では、構文が次のように変更されました。

class SuperClass(metaclass=Watcher)

両方とも同等です

Superclass = Watcher(name, bases, clsdict)

この場合、は stringにname等しく、は tupleです。は、クラス定義の本体で定義されたクラス属性のディクショナリです。'Superclass'bases(object, )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
于 2013-08-08T12:57:57.760 に答える
11

編集: 私の古い投稿は実際には機能しませんでした。からのサブクラス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

最後に、情報に作用するメタクラスが必要です。ほとんどの場合、ここで行う最も興味深いことは、提供された各ベースでフックをチェックすることです。このように、super は最も驚くべき方法で機能します。

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
于 2013-08-08T13:02:59.820 に答える