Как запустить код, когда класс является подклассом? [дубликат]


этот вопрос уже есть ответ здесь:

есть ли способ вызвать код, когда мой класс является подклассом?

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
2 69

2 ответа:

классы (по умолчанию) являются экземплярами 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)

где в данном случае name равна строке '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

Edit: мой старый пост на самом деле не работал. Подклассы из classmethod не работает, как ожидалось.

во-первых, мы хотели бы иметь какой-то способ сообщить метаклассу, что этот конкретный метод должен иметь специальный вызов поведения подкласса, мы просто установим атрибут функции, которую мы хотели бы вызвать. Для удобства мы даже превратим функцию в classmethod так что реальный базовый класс, в котором он был найден, также может быть обнаружен. Мы вернем classmethod так именно его можно использовать в качестве декоратора, что наиболее удобно.

import types
import inspect

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

мы также будем хотеть удобный способ увидеть, что декоратор. Мы знаем, что classmethod был использован, так что мы будем проверять это, и только потом искать .

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