3

I have a situation where I want be able to call a commonly used method of an instance variable from the instance itself. This isn't really necessary for the program to function; however, it's much more convenient to type bar.baz() as opposed to bar.foo.baz().

What would be the most Pythonic/readable/most common way to approach this? I was thinking I'd write a function decorator; however, I'm open to other ideas. The solution must also be inheritance friendly.

Example:

class Foo (object) :
    def baz (self) :
        return 324

class Bar (object) :
    def __init__ (self) :
        self.foo = Foo()

    # This is the current ugly solution.
    # It also results in an extra function call.
    def baz (self, *args, **kw) :
        return self.foo.baz(*args, **kw)

bar = Bar()
print bar.foo.baz()
print bar.baz() 

This is for Python 2.7.3, FYI.

1
  • 1
    But if baz is just a utility method and has no state in relation to Bar then you don't need a class. You just need functions Commented Aug 10, 2012 at 5:38

3 Answers 3

4

You could just do what is called a mixin. You create a class that really has no personal state, but carries common functionalities that you want to "mix in" to another class:

class FooMixin(object) :
    def baz (self) :
        return 324

class Bar(object, FooMixin):
    def __init__ (self):
        pass

bar = Bar()
bar.baz()

*Or use composition like @Joachim suggests in the other answer.

Sign up to request clarification or add additional context in comments.

1 Comment

Presumably though the "real" version of Foo would have methods that do depend on some internal state.
2

This is simpler;

class Foo (object) :
    def baz (self) :
        return 324

class Bar (object) :
    def __init__ (self) :
        self.foo = Foo()
        self.baz = self.foo.baz;

bar = Bar()
print bar.foo.baz()
print bar.baz()

5 Comments

This was actually my first approach to the problem. However this solution doesn't work well with inheritance, so it's off the table.
@RectangleTangle: What aspect doesnt work well for inheritance?
If I were to make a class inheriting from Bar called Buzz, with its own method name baz, Buzz.baz would be overwritten when Bar's __init__ method is called.
@RectangleTangle: then make Foo the baseclass for everything. or use my mixin approach
@RectangleTangle Yes, I went for the simplest possible solution for your example. When inheritance is involved, mixins (@jdi's solution) are indeed the "pythonic" way to do it.
1

If it's just the one method, then what you've got is honestly not that bad. It quite straightforwardly says that baz is part of the interface of Boo, and it achieves this by delegating to the contained Foo instance.

A slightly shorter version would be to use a property:

class Foo (object) :
    def baz (self) :
        return 324

class Bar (object) :
    def __init__ (self) :
        self.foo = Foo()

    @property
    def baz(self):
        return self.foo.baz

    def __getattr__(self, attr):
        return getattr(self.foo, attr)

bar = Bar()
print bar.foo.baz()
print bar.baz()

There's still an extra function call though, and it's perhaps slightly less obvious that baz is a method.

Without resorting to rewriting your inheritance tree, I'd just go with what you have if baz is the only method that needs to be delegated to the contained instance like that. If you want to delegate many methods, then there are some options:

If you want all methods of Foo to be callable from the Bar instance that contains a Foo, then you could try using __getattr__:

class Foo (object) :
    def baz (self) :
        return 324

class Bar (object) :
    def __init__ (self) :
        self.foo = Foo()

    def __getattr__(self, attr):
        return getattr(self.foo, attr)

bar = Bar()
print bar.foo.baz()
print bar.baz()

But this has some obvious drawbacks.

  1. It's not clear what methods Bar actually provides, either by reading the source code or by introspection.
  2. All of Foo's methods and attributes (that aren't provided directly by Bar) will be exposed through Bar's interface, which may be undesirable.

Of course, you could put more logic in the __getattr__ method to only delegate certain things, not everything.

I tend to go with this sort of approach when all a Bar is is a simple wrapper around a Foo, because then "a Bar behaves mostly like a Foo" is part of the conceptual interface (rather than Foo being an internal implementation detail), and it reduces Bar's definition to describing the ways it is not like a Foo.

Another approach is to use Python's dynamic nature to define properties on Bar that delegate to Foo without having to handwrite them all. Something like this would do the trick:

class Foo (object) :
    def baz (self) :
        return 324

class Bar (object) :
    def __init__ (self) :
        self.foo = Foo()

    for _method in ['baz', 'blob']:
        @property
        def _tmp_func(self, _name=_method):
            return getattr(self.foo, _name)
        locals()[_method] = _tmp_func
    # del to avoid name pollution
    del _method, _tmp_func

bar = Bar()
print bar.foo.baz()
print bar.baz()

The advantages of this approach over the __getattr__ one are that you've got an explicit list of methods that are redirected when you're reading the code, and they'll all "look like" normal properties of Bar at runtime because they are normal properties. It's obviously a lot more code than your original if you only need this for one or two methods though! And it's a little subtle (the need to pass _method into _tmp_func via a default argument because of the way Python's closures work isn't necessarily obvious). But it would be relatively easy to hide package up the logic for doing this in a class decorator, which would gain you the ability to create more classes that delegate the implementation of some of their methods to one of their attributes.

There are many variations on these sorts of things you can come up with.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.