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.
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.
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 likedir()
orhelp()
, 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.This complexity is perceived as un-Pythonic. “Explicit is better than implicit.”
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.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: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.