How to run code when a class is subclassed?

This question already has an answer here:

  • What are metaclasses in Python? 14 answers

  • Classes (by default) are instances of type . Just as an instance of a class Foo is created by foo = Foo(...) , an instance of type (ie 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 -- ie type . The way to do that is to define a subclass of type -- ie 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
    

    Edit: My old post actually didn't work. Subclassing from classmethod doesn't work as expected.

    First, we would like to have some way to tell the metaclass that this particular method is supposed to have the special called on subclass behavior, we'll just set an attribute on the function we'd like to call. As a convenience, we'll even turn the function into a classmethod so that the real baseclass it was found in can be discovered, too. We'll return the classmethod so that it can be used as a decorator, which is most convenient.

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

    We're also going to want a convenient way to see that the subclass_hook decorator was used. We know that classmethod has been used, so we'll check for that, and only then look for the is_subclass_hook attribute.

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

    Finally, we need a metaclass that acts on the information: For most cases, the most interesting thing to do here is just check each of the supplied bases for hooks. In that way, super works in the least surprising way.

    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)
    

    and that should do it.

    >>> 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
    
    链接地址: http://www.djcxy.com/p/6980.html

    上一篇: 了解Python中的元类和继承

    下一篇: 当一个类被子类化时如何运行代码?