Reputation: 43
I want to create instances of several classes at runtime, based on configuration xml files which contain the specific class type and other stuff. Let's start with a simple example:
class ParentClass:
@staticmethod
def create_subclass(class_type: str, xml):
if class_type == 'childA':
return ChildClassA(xml)
elif class_type == 'childB':
return ChildClassB(xml)
else:
return ParentClass(xml)
def __init__(self, xml):
print('\nParent stuff done.')
class ChildClassA(ParentClass):
def __init__(self, xml):
super().__init__(xml)
print('Class A stuff done.')
class ChildClassB(ParentClass):
def __init__(self, xml):
super().__init__(xml)
print('Class B stuff done.')
ParentClass.create_subclass('childA', None)
ParentClass.create_subclass('childB', None)
ParentClass.create_subclass('unknown', None)
The output is as expected:
Parent stuff done.
Class A stuff done.
Parent stuff done.
Class B stuff done.
Parent stuff done.
As the hierarchy grows larger I want to have the parent class in a separate module and each child class in a separate module. Obviously, I need to import the parent module to the child class modules. Due to the create subclass
method, the parent class module needs to know about the child classes. Importing those to the parent module would cause a circular import which is considered bad design.
What would be a good solution to solve this issue? Are there better ways to create the subclass instances?
Upvotes: 0
Views: 169
Reputation: 486
You can use some form of Registry
:
from typing import Dict
class Registry:
_instance: 'Registry' = None
def __init__(self):
self._classes: Dict[str, type] = {}
self._default: type = object
@classmethod
def get_instance(cls) -> 'Registry':
if cls._instance is None:
cls._instance = Registry()
return cls._instance
def register(self, name: str, cls: type) -> None:
self._classes[name] = cls
def set_default(self, cls: type) -> None:
self._default = cls
def access(self, name: str) -> type:
cls = self._classes.get(name)
if cls is None:
return self._default
return cls
then you can set the parent class as its default class.
from .registry import Registry
class ParentClass:
def __init__(self, xml):
print('\nParent stuff done.')
registry = Registry.get_instance()
registry.set_default(ParentClass)
and submit child classes to your registry after defining them.
from .registry import Registry
class ChildClassA(ParentClass):
def __init__(self, xml):
super().__init__(xml)
print('Class A stuff done.')
registry = Registry.get_instance()
registry.register('childA', ChildClassA)
from .registry import Registry
class ChildClassB(ParentClass):
def __init__(self, xml):
super().__init__(xml)
print('Class B stuff done.')
registry = Registry.get_instance()
registry.register('childB', ChildClassB)
Cause your registry doesn't depend on your classes, you can use it without worrying about circulaNoticerts. notice that the access
method returns a type
, and you must call the constructor yourself.
from .registry import Registry
registry = Registry.get_instance()
registry.access('childA')(xml=None)
# Parent stuff done.
# Class A stuff done.
registry.access('childB')(xml=None)
# Parent stuff done.
# Class B stuff done.
registry.access('unknown')(xml=None)
# Parent stuff done.
You can also access subclasses and their names dynamically (as answered here) but since you didn't use subclass names as your key when creating subclasses, you probably need some mapping between names and subclasses. If you want your subclasses to tell your parent class the name they wish to be called by, you are implementing Registry
in your parent class again.
accessing subclass names:
print([cls.__name__ for cls in ParentClass.__subclasses__()])
# ['ChildClassA', 'ChildClassB']
accessing subclasses themselves:
print(ParentClass.__subclasses__())
# [<class '__main__.ChildClassA'>, <class '__main__.ChildClassB'>]
Upvotes: 1