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

Classes (by default) are instances of type. Just as an instance of a class Foo is created by foo = Foo(...), an instance of type (i.e. a class) is created by myclass = type(name, bases, clsdict).

If you want something special to happen at the moment of class-creation, then you have to modify the thing creating the class -- i.e. type. The way to do that is to define a subclass of type -- i.e. a metaclass.

A metaclass is to its class as a class is to its instance.

In Python2 you would define the metaclass of a class with

class SuperClass:
    __metaclass__ = Watcher

where Watcher is a subclass of type.

In Python3 the syntax has been changed to

class SuperClass(metaclass=Watcher)

Both are equivalent to

Superclass = Watcher(name, bases, clsdict)

where in this case, name equals the string 'Superclass', and bases is the tuple (object, ). The clsdict is a dictionary of the class attributes defined in the body of the class definition.

Note the similarity to myclass = type(name, bases, clsdict).

So, just as you would use a class's __init__ to control events at the moment of a instance's creation, you can control events at the moment of a class's creation with a metaclass's __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")

prints

foo
was subclassed by SubClass0
bar
test
was subclassed by SubClass1
于 2013-08-08T12:57:57.760 回答
11

编辑:我的旧帖子实际上没有用。子类化从不classmethod按预期工作。

首先,我们想用某种方式告诉元类这个特定的方法应该具有对子类行为的特殊调用,我们只需在我们想要调用的函数上设置一个属性。为方便起见,我们甚至将函数转换为 aclassmethod以便可以发现它所在的真正基类。我们将返回 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 回答