Home > Software design >  Pythonic way of passing on methods when using composition instead of inheritance
Pythonic way of passing on methods when using composition instead of inheritance

Time:07-05

Suppose I have a library with something like the following, where Person is a user-facing class.

class SwissArmyKnife:

    def open_bottle(self):
        """...etc...."""

    def open_package_with_scissors(self):
        """...etc...."""

    def uncork_wine(self):
        """...etc...."""

    def whittle_an_intricate_dolphin_figurine(self):
        """...etc...."""

class Person:
    
    def __init__(self):
        self.swiss_army_knife = SwissArmyKnife()
        
    def open_bottle(self):
        self.swiss_army_knife.open_bottle()

    def open_package_with_scissors(self):
        self.swiss_army_knife.open_package_with_scissors()

    def uncork_wine(self):
        self.swiss_army_knife.uncork_wine()

    def whittle_an_intricate_dolphin_figurine(self):
        self.swiss_army_knife.whittle_an_intricate_dolphin_figurine()

I want to pass along all of the methods of SwissArmyKnife to Person, since the Person will have a SwissArmyKnife, but ideally, I'd like to avoid all of the boilerplate code in the Person class, since every time I update what a swiss army knife can do, I'd have to remember to update the person class as well.

One way of doing this would be to have Person inherit from SwissArmyKnife, but that feels pretty awkward, since a person isn't a swiss army knife; they just have one.

Another possibility would be just to expect users to write person.swiss_army_knife.open_bottle(), but that's a little verbose. Also, in the actual case that's leading me to ask this toy question, I've already released a version of my library in which you can just write person.open_bottle() and I don't want to break backwards compatibility.

Is there a good way that I'm not seeing to autopopulate the methods of SwissArmyKnife to Person? What's the pythonic thing to do?

CodePudding user response:

If you really, really, really want to do this, you can overload the special __getattr__ method. According to the documentation,

__getattr__ is called when the default attribute access fails with an AttributeError (either __getattribute__() raises an AttributeError because name is not an instance attribute or an attribute in the class tree for self; or __get__() of a name property raises AttributeError).`

So you could automatically forward any undefined method call to the Swiss Army knife something like this:

class SwissArmyKnife:
    def doit(self):
        print("I done it!")

class Person:
    def __init__(self):
        self.swiss_army_knife = SwissArmyKnife()

    def say_howdy(self):
        print("howdy")

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

p = Person()
p.say_howdy()
p.doit()
<script src="https://cdn.jsdelivr.net/gh/pysnippet/pysnippet@latest/snippet.min.js"></script>

CodePudding user response:

If your objection to using inheritance is that it seems semantically confusing, you might consider the Mixin pattern instead. The way to do Mixins in Python still uses inheritance, but the pattern conveys that the relationship is one of "has the functionality of" rather than "is a".

This StackOverflow question has a good discussion on this topic for Python.

  • Related