Delegation Pattern in Python – Is It Unpopular or Not Pythonic?

delegationdesign-patternspython

As a Ruby/Rails person, I often deal with method delegation. It's often convenient to delegate a method to a composed object or a constant. Ruby even has a def_delegator helper method to easily build delegators and Rails' ActiveSupport improves the builtin's interface with delegate.

Yet when I looked at Python, I found no such helpers to create delegation, neither in stdlib nor on PyPi. The best I can find is some recommendation on StackOverflow to manually override __getitem__ to declare delegators.

So I have to ask: is this pattern considered non-Pythonic? Or am I missing something?

Thank you.

Best Answer

While delegation is possible, it is indeed not common.

  1. In many cases where you might reach to delegation, you could also use multiple inheritance. Ruby doesn't have multiple inheritance, therefore it must use other approaches such as mixins or delegation. Python has more flexible semantics here.

  2. Delegation based on __getattr__ and/or __getattribute__ is complex, and needs a fair understanding of Python's low-level details to implement properly. This interacts with the descriptor protocol, and introspection features like dir() or help(), and with other dunder methods such as __str__ or __add__: you cannot implicitly delegate dunder methods, but would have to implement a method that performs the delegation.

  3. This complexity is perceived as un-Pythonic. “Explicit is better than implicit.”

  4. Instead of delegation through __getattr__, you could just manually implement the methods or properties that you want to delegate. You can use metaprogramming (via metaclasses or class decorators) to simplify this, or use the descriptor protocol to create class members that perform the delegation when accessed through an instance.

  5. Delegation is slow, especially when based on reflection or metaprogramming features.

Descriptors are objects that implement a class member. They are invoked in different ways depending on whether a class or instance member is accessed. Functions and properties are commonly used descriptor implementations, but user-defined descriptors can be created. Anything that has a __get__ method may be used as a descriptor. In one project I implemented a descriptor to perform delegation, e.g. used like:

class Foo:
  delegated_method = delegate('TargetType', 'delegated_method', to='_target')

  def __init__(self, target):
    self._target = target

# Foo().delegated_method => Foo()._target.delegated_method
# Foo.delegated_method => returns the descriptor
# help(Foo.delegated_method)  # works

In the end, the complexity and performance impact of using descriptors was too much in that project, and I just spelled out the delegation explicitly instead.

Related Topic