关于python:当类是子类时如何运行代码?

How to run code when a class is subclassed?

本问题已经有最佳答案,请猛点这里访问。

当我的类是子类时,是否有触发代码的方法?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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")

应该输出

1
2
3
4
5
foo
was subclassed by SubClass0
bar
test
was subclassed by SubClass1


类(默认情况下)是type的实例。正如Foo类的实例是由foo = Foo(...)创建的一样,type的实例(即类)是由myclass = type(name, bases, clsdict)创建的。

如果您希望在创建类的时候发生一些特殊的事情,那么您必须修改创建类的东西——即type。这样做的方法是定义type的一个子类——即元类。

元类是对其类的,类是对其实例的。

在python2中,您将使用

1
2
class SuperClass:
    __metaclass__ = Watcher

其中,Watchertype的一个子类。

在python3中,语法已更改为

1
class SuperClass(metaclass=Watcher)

两者都等同于

1
Superclass = Watcher(name, bases, clsdict)

在这种情况下,name等于字符串'Superclass'bases是元组(object, )clsdict是类定义主体中定义的类属性的字典。

注意与myclass = type(name, bases, clsdict)的相似性。

因此,就像使用类的__init__来控制实例创建时的事件一样,可以使用元类的__init__来控制类创建时的事件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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")

印刷品

1
2
3
4
5
foo
was subclassed by SubClass0
bar
test
was subclassed by SubClass1


编辑:我以前的帖子实际上不起作用。从classmethod中进行子类化并没有按预期工作。

首先,我们想用某种方法来告诉元类,这个特定的方法应该对子类行为有特殊的调用,我们只需要在要调用的函数上设置一个属性。为了方便起见,我们甚至将把函数转换成一个classmethod,这样就可以发现它所在的真正基类。我们将返回ClassMethod,以便它可以用作最方便的修饰器。

1
2
3
4
5
6
import types
import inspect

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

我们还需要一个方便的方法来查看使用了subclass_hook装饰器。我们知道已经使用了classmethod,所以我们将检查它,然后才查找is_subclass_hook属性。

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

最后,我们需要一个对信息起作用的元类:对于大多数情况,这里要做的最有趣的事情就是检查每个提供的钩子基。这样一来,Super的工作方式就不足为奇了。

1
2
3
4
5
6
7
8
9
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)

这样就可以了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> 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