I recently stumbled across metaclasses in Python and decided to use them in order to simplify some features. (Using Python 3.5)
In a nutshell, I'm writing a module defining classes such as "components" that must be registered and initialized (I mean I need to initialize the actual class, not an instance).
I can register the class easily:
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"""
In this case, I'm registering the class of the component, as it allows me to refer to them later, without their actual reference.
But classes lack the ability to initialize themselves (at least in Python 3.5), and can cause some problems, like such:
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
Now, this is a problem, because the idea was to have a unique list of sub-managers per manager. But the SubManager field is shared across each subclasses... So adding to one's list adds to every's. RIP.
So the next idea was to implement a kind of initializator:
class BaseManager(Component):
@classmethod
def classinit(cls):
cls.SubManagers = []
But now, I need a way to call this method after the class creation. So lets do this with metaclasses again:
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)
I would consider myself done with this. Somewhat of an elegant way to do it IMO. The classinit() would be called from each class being created (unlike the 3.6 __init_subclass__ that is called on the parent). At least I liked it, until Python 3.5 cried in RuntimeError: super(): empty __class__ cell...
I read it was because I was calling a method from the metaclass's __init__ method and that although the class was created (hence my will to put code in __init__, to initialize something already created), it lacks this __class__ cell, at least at that moment...
I tried running the exact same code in Python 3.6, and it worked, so I guess something was wrong but got fixed...
My real questions are:
- Can we truly initialize classes with metaclasses in Python 3.5 ?
- Is there a way to avoid the use-restriction of super() in the initialization procedure?
- Why is it working in 3.6?
- If everything should fail, what would be the best course of action to still provide the class initialization, and allowing for super(...) calls? (Like do I need to refer to the super class explicitly ?)
Thanks for your help in advance.
EDIT:
The goal is to be able to derive components and be able to initialize each one's class relative to its parent, in an "easy" way:
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
Managers are one concern, I have multiple concepts that depend on components and their ability to initialize their class.
@Manager2.RegisterSubManagercan possibly work, sinceManager2isn't a subclass ofManager1.