Недавно я наткнулся на метаклассы в Python и решил использовать их, чтобы упростить некоторые функции. (Используя Python 3.5)
Короче говоря, я пишу модуль, определяющий классы, такие как «компоненты», которые должны быть зарегистрированы и инициализированы (я имею в виду, что мне нужно инициализировать фактический класс, а не экземпляр).
Я могу легко зарегистрировать класс:
class MetaComponent(type):
def __init__(cls, *args, **kargs):
super().__init__(*args, **kargs)
RegisterComponent(cls)
class BaseComponent(metaclass=MetaComponent):
pass
class Component(BaseComponent):
"""This is the actual class to use when writing components"""
В этом случае я регистрирую класс компонента, так как это позволяет мне ссылаться на них позже, без их фактической ссылки.
Но классы не имеют возможности инициализировать себя (по крайней мере, в Python 3.5) и могут вызвать некоторые проблемы, например:
class Manager(Component):
SubManagers = []
@classmethod
def ListSubManagers(cls):
for manager in cls.SubManagers:
print(manager)
@classmethod
def RegisterSubManager(cls, manager):
cls.SubManagers.append(manager)
return manager
@Manager1.RegisterSubManager
class Manager2(Manager):
pass
@Manager2.RegisterSubManager
class Manager3(Manager):
pass
# Now the fun:
Manager1.ListSubManagers()
# Displays:
# > Manager2
# > Manager3
Теперь это проблема, потому что идея заключалась в том, чтобы иметь уникальный список подчиненных менеджеров для каждого менеджера. Но поле SubManager
является общим для всех подклассов... Таким образом, добавление в один список добавляет к каждому. РВАТЬ.
Поэтому следующей идеей было реализовать своего рода инициализатор:
class BaseManager(Component):
@classmethod
def classinit(cls):
cls.SubManagers = []
Но теперь мне нужен способ вызвать этот метод после создания класса. Итак, давайте снова сделаем это с метаклассами:
class MetaComponent(type):
def __init__(cls, *args, **kargs):
super().__init__(*args, **kargs):
cls.classinit(**kargs)
RegisterComponent(cls)
class BaseComponent(metaclass=MetaComponent):
@classmethod
def classinit(cls, **kargs):
print('BASE', cls)
class Component(BaseComponent):
@classmethod
def classinit(cls, **kargs):
super().classinit(**kargs) # Being able to use super() is the goal
print('COMPONENT', cls)
Я бы считал, что с этим покончено. Несколько элегантный способ сделать это ИМО. classinit()
будет вызываться из каждого создаваемого класса (в отличие от 3.6 __init_subclass__
, который вызывается для родителя). По крайней мере, мне это нравилось, пока Python 3.5 не заплакал в RuntimeError: super(): empty __class__ cell
...
Я читал, что это было потому, что я вызывал метод из метода __init__
метакласса, и хотя класс был создан (отсюда мое желание поместить код в __init__
, чтобы инициализировать что-то уже созданное), ему не хватает этой ячейки __class__
, по крайней мере, в тот момент ...
Я попытался запустить точно такой же код в Python 3.6, и это сработало, поэтому я предполагаю, что что-то было не так, но это было исправлено...
Мои настоящие вопросы:
- Можем ли мы действительно инициализировать классы с помощью метаклассов в Python 3.5?
- Есть ли способ избежать ограничения использования super() в процедуре инициализации?
- Почему это работает в 3.6?
- Если все потерпит неудачу, как лучше всего по-прежнему обеспечивать инициализацию класса и разрешать вызовы super(...)? (Например, мне нужно явно ссылаться на суперкласс?)
Заранее благодарны за Вашу помощь.
ИЗМЕНИТЬ:
Цель состоит в том, чтобы иметь возможность создавать компоненты и иметь возможность инициализировать каждый класс относительно его родителя "простым" способом:
class Manager(Component):
def classinit(cls, **kargs):
cls.SubManagers = []
@classmethod
def RegisterSubManager(cls, manager):
cls.SubManagers.append(manager)
return manager
@Manager.RegisterSubManager
class EventManager(Manager):
def classinit(cls, **kargs):
super().classinit(**kargs) # keep the old behaviour
cls.Events = []
# ...
@EventManager.RegisterSubManager
class InputManager(EventManager):
def classinit(cls, **kargs):
super().classinit(**kargs) # again, keep old behaviour
cls.Inputs = []
# use parts of EventManager, but define specialized methods
# for input management
Менеджеры - это одна проблема, у меня есть несколько концепций, которые зависят от компонентов и их способности инициализировать свой класс.
@Manager2.RegisterSubManager
может работать, посколькуManager2
не является подклассомManager1
. - person Blckknght   schedule 25.08.2017