Как запустить код, когда класс является подклассом? [дубликат]
этот вопрос уже есть ответ здесь:
- что такое метаклассы в Python? 17 ответов
есть ли способ вызвать код, когда мой класс является подклассом?
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 ответа:
классы (по умолчанию) являются экземплярами
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