I have a quite complex class hierarchy in my Python program. The program has many tools, which are either simulators or compilers. Both kinds share some methods, so there is a Shared
class as a base class for all classes. A strip-down example looks like this:
class Shared:
__TOOL__ = None
def _Prepare(self):
print("Preparing {0}".format(self.__TOOL__))
class Compiler(Shared):
def _Prepare(self):
print("do stuff 1")
super()._Prepare()
print("do stuff 2")
def _PrepareCompiler(self):
print("do stuff 3")
self._Prepare()
print("do stuff 4")
class Simulator(Shared):
def _PrepareSimulator(self): # <=== how to create an alias here?
self._Prepare()
class Tool1(Simulator):
__TOOL__ = "Tool1"
def __init__(self):
self._PrepareSimulator()
def _PrepareSimulator(self):
print("do stuff a")
super()._PrepareSimulator()
print("do stuff b")
Can I define method Simulator._PrepareSimulator
as an alias to Simulator/Shared._Prepare
?
I know I can create local aliases like: __str__ = __repr__
, but in my case _Prepare
is not known in the context. I have no self
nor cls
to reference this method.
Could I write a decorator to return _Prepare
instead of _PrepareSimulator
? But how would I find _Prepare
in the decorator?
Do I need to adjust the method binding too?
I managed to create a decorator based solution, which ensures type safety. The first decorator annotates an alias to a local method. It is needed to safe the aliases target. The second decorator is needed to replace the Alias
instance and check if the alias points to a method in the type hierarchy.
Decorators / Alias definition:
from inspect import getmro
class Alias:
def __init__(self, method):
self.method = method
def __call__(self, func):
return self
def HasAliases(cls):
def _inspect(memberName, target):
for base in getmro(cls):
if target.__name__ in base.__dict__:
if (target is base.__dict__[target.__name__]):
setattr(cls, memberName, target)
return
else:
raise NameError("Alias references a method '{0}', which is not part of the class hierarchy: {1}.".format(
target.__name__, " -> ".join([base.__name__ for base in getmro(cls)])
))
for memberName, alias in cls.__dict__.items():
if isinstance(alias, Alias):
_inspect(memberName, alias.method)
return cls
Usage example:
class Shared:
__TOOL__ = None
def _Prepare(self):
print("Preparing {0}".format(self.__TOOL__))
class Shared2:
__TOOL__ = None
def _Prepare(self):
print("Preparing {0}".format(self.__TOOL__))
class Compiler(Shared):
def _Prepare(self):
print("do stuff 1")
super()._Prepare()
print("do stuff 2")
def _PrepareCompiler(self):
print("do stuff 3")
self._Prepare()
print("do stuff 4")
@HasAliases
class Simulator(Shared):
@Alias(Shared._Prepare)
def _PrepareSimulatorForLinux(self): pass
@Alias(Shared._Prepare)
def _PrepareSimulatorForWindows(self): pass
class Tool1(Simulator):
__TOOL__ = "Tool1"
def __init__(self):
self._PrepareSimulator()
def _PrepareSimulator(self):
print("do stuff a")
super()._PrepareSimulatorForLinux()
print("do stuff b")
super()._PrepareSimulatorForWindows()
print("do stuff c")
t = Tool1()
Setting @Alias(Shared._Prepare)
to @Alias(Shared2._Prepare)
will raise an exception:
NameError: Alias references a method '_Prepare', which is not part of the class hierarchy: Simulator -> Shared -> object.