Paebbels Paebbels - 19 days ago 5
Python Question

Can I create an alias to an inherited method in Python?

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?

Answer

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.