Search code examples
pythonpython-3.xmethodsaliaspython-decorators

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?


Solution

  • 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.