0

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?

MarcTheSpark
  • 473
  • 5
  • 14
  • "I want to pass along all of the methods of SwissArmyKnife to Person, since the Person will have a SwissArmyKnife" - this suggests you may need to rethink how responsibilities are separated in your design. Giving a Person all the methods of its SwissArmyKnife sounds like responsibilities aren't being divided cleanly. – user2357112 Jul 03 '22 at 04:18

2 Answers2

2

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>
Denise Draper
  • 1,027
  • 10
  • 24
  • This is a clever solution, and does exactly what I'm asking! It does have one major drawback, though, which is that autocomplete will never know that a Person can open a bottle. I'm guessing there's not much that can be done about that, but it's unfortunate, since that's an important convenience to users of the library. – MarcTheSpark Jul 03 '22 at 06:09
  • I think the only way you'd get autocomplete would be to use inheritance. Autocomplete relies on _static_ properties of the class, so no dynamic method will work. – Denise Draper Jul 03 '22 at 06:20
  • 1
    ...Or, now that I think of it, treat SwissArmyKnife as a Mixin. See, e.g. [here](https://www.residentmar.io/2019/07/07/python-mixins.html#:~:text=Mixins%20are%20an%20alternative%20class,this%20feature%E2%80%94and%20nothing%20else)) – Denise Draper Jul 03 '22 at 06:27
  • Interesting. I guess that's basically like using inheritance, except that it keeps the functionality clearly separated conceptually. I think this might be the solution I was looking for! – MarcTheSpark Jul 03 '22 at 17:12
1

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.

Denise Draper
  • 1,027
  • 10
  • 24