Mastering the Easy Art of Delegation in Python: A Step-by-Step Guide

Introduction

Delegation is like passing a job to someone else. In Python, you can do this by using a method called __getattr()__. It’s almost like asking one object to do a task for another object, just like a subclass asking its parent class for help. Here you can find an article explaining this in more detail.

Implementation in Python

Let’s say we want to create a windowing system. We have a basic window that can open and close. We want to add more features to it, like rotating the window.

To make this happen, we create a WindowsDelegator class, which helps other classes delegate their work. In our case, we’ll use it with the BasicWindow class:

class WindowsDelegator:
    _delegate: object = None

    def __init__(self):
        self._delegate = self.get_delegate()

    def get_delegate(self) -> object:
        raise NotImplementedError

    def __getattr__(self, name):
        return getattr(self._delegate, name)

Here’s what’s happening:

  • We need something to delegate to, called _delegate.
  • In the constructor, we call the get_delegate() method to set this attribute.
  • The get_delegate() method is not fully implemented here because it needs to be defined in subclasses.
  • We define the __getattr() method, which tries to find an attribute in the _delegate object. If it can’t find it, it raises an exception.

Now let’s look at the BasicWindow class:

class BasicWindow(WindowsDelegator):
    _title: str = None

    def __init__(self, title: str):
        super().__init__()
        self._title = title

    def open(self):
        print(f"Opening window with title: {self._title}")

    def close(self):
        print(f"Closing window with title: {self._title}")

    @property
    def title(self) -> str:
        return self._title

    def get_delegate(self) -> object:
        return ExtendedWindow(self)

Here’s what’s happening:

  • BasicWindow uses the WindowsDelegator.
  • It has a title, open, and close methods.
  • The get_delegate() method returns an instance of the ExtendedWindow class.

Finally we can implement the ExtendedWindow class:

class ExtendedWindow:
    _window: BasicWindow = None

    def __init__(self, window: BasicWindow):
        self._window = window

    def rotate(self, degrees: int):
        print(f"Rotating window with title {self._window.title} by {degrees} degrees")

A summary:

  1. We embed a BasicWindow in this class to be able to access its properties, which we will need as you shall see.
  2. In the constructor we set the appropiate values
  3. In the rotate() method access the title of the BasicWindow title property to print out a message.

Testing time

The proof is in the pudding, so let’s test it:

if __name__ == "__main__":
    w = BasicWindow("My window")
    w.open()
    w.rotate(45)
    w.close()

A summary:

  1. Create a BasicWindow
  2. We open, rotate and close this window. Notice that the call to rotate() automatically redirects to the functionality in ExtendedWindow

Conclusion

In conclusion, while delegation is possible in Python, it may not always be the most Pythonic way to achieve your goals. It relies on low-level details and can become complex and less readable. If you can find a simpler way to do the same task, that might be a better choice.

Leave a Reply

Your email address will not be published. Required fields are marked *