29

I have a class that represents object. And I have a bunch of methods which modify this object state with no obvious return or obviously without any return. In C# I would declare all these methods as void and see no alternatives. But in Python I am about to make all the methods return self to give myself ability to write awesome one-liners like this:

classname().method1().method2().method3()

Is this Pythonic, or otherwise acceptable in Python?

Will
  • 24,082
  • 14
  • 97
  • 108
Philip B
  • 319
  • 1
  • 3
  • 9
  • 1
    See http://programmers.stackexchange.com/questions/29657/purpose-of-return-self-from-a-class-method – ilim Mar 27 '16 at 18:31
  • 8
    this technique is called fluent interface. see: https://en.wikipedia.org/wiki/Fluent_interface – Alexander Oh Mar 27 '16 at 18:34
  • 3
    Is a question about best practices appropriate for Stack Overflow? It seems better suited to Programmers or Code Review. I'm inclined to say "is this a good or bad technique" is too broad here. – Waleed Khan Mar 27 '16 at 18:40
  • 2
    @WaleedKhan This question would be closed as hypothetical on [Code Review](http://codereview.stackexchange.com/help/on-topic). – nhgrif Mar 27 '16 at 18:41
  • I guess I'm confused about whether the question is asking "are fluent interfaces good" or "what's the best way to implement a fluent interface in Python". – Waleed Khan Mar 27 '16 at 18:46
  • 1
    Well, neither of those questions would be a good fit for [Code Review](http://codereview.stackexchange.com/help/on-topic), @WaleedKhan. They *might* be fits for Programmers... but you should definitely double check *their* help center (I'm not really an active Programmers user). – nhgrif Mar 27 '16 at 18:48
  • To ask this question on [Code Review](http://codereview.stackexchange.com/help/on-topic), it'd need to be in the format of "Here is my fluent interface. Is it well-implemented?" – nhgrif Mar 27 '16 at 18:49
  • I meant Programmers! Feel free to delete your comments as that was mistake :). – Floam Mar 27 '16 at 18:50
  • 2
    See http://programmers.stackexchange.com/questions/69519/when-to-go-fluent-in-c/69534#69534 (on C#, but probably mostly applies for Python) – Stuart Mar 27 '16 at 18:54
  • @WaleedKhan when referring other sites, it is often helpful to point that [cross-posting is frowned upon](http://meta.stackexchange.com/tags/cross-posting/info) – gnat Mar 27 '16 at 19:00
  • It pains me when one of the few good questions such as this one is put on hold as too broad. This isn't too broad, quite simply. – miradulo Mar 28 '16 at 08:34
  • @DonkeyKong Frankly, I **still** have no idea what this question is asking. It has been a long time since the question has been posted and it has not been clarified. The answers range from explaining why Python's interfaces are as they are, recommending fluent interfaces with a citation of a real library, and several implementations. – Waleed Khan Mar 28 '16 at 16:34
  • @WaleedKhan I have nothing to specify, question as broad as intended. I was waited for answers like: _"There is no unified opinion about this approach. It has pros: ... and cons: .... In Python it considered to be a bad practice, but in Lisp methods return self by default."_ Seems like it wasn't a proper question for SO format. My bad. – Philip B Mar 28 '16 at 19:29

4 Answers4

32

Here is a mail from Guido van Rossum (the author of the Python programming language) about this topic: https://mail.python.org/pipermail/python-dev/2003-October/038855.html

I'd like to explain once more why I'm so adamant that sort() shouldn't return 'self'.

This comes from a coding style (popular in various other languages, I believe especially Lisp revels in it) where a series of side effects on a single object can be chained like this:

x.compress().chop(y).sort(z)

which would be the same as

x.compress() x.chop(y) x.sort(z)

I find the chaining form a threat to readability; it requires that the reader must be intimately familiar with each of the methods. The second form makes it clear that each of these calls acts on the same object, and so even if you don't know the class and its methods very well, you can understand that the second and third call are applied to x (and that all calls are made for their side-effects), and not to something else.

I'd like to reserve chaining for operations that return new values, like string processing operations:

y = x.rstrip("\n").split(":").lower()

There are a few standard library modules that encourage chaining of side-effect calls (pstat comes to mind). There shouldn't be any new ones; pstat slipped through my filter when it was weak.

sentence
  • 8,213
  • 4
  • 31
  • 40
Querenker
  • 2,242
  • 1
  • 18
  • 29
  • Where can I read about that convention? – Philip B Mar 27 '16 at 19:08
  • 1
    I found a mail from Guido van Rossum (the author of the Python programming language) about this topic. – Querenker Mar 27 '16 at 20:53
  • The other major example of course is Django - where the tutorial positively encourages chaining. Far being from me to disagree with the BDFL - but i don't find it a threat to readability at all - if the API is done well (which I think DJango is for the most part). – Tony Suffolk 66 Mar 28 '16 at 07:56
  • 6
    @TonySuffolk66: but Django query methods produce a *new object* each time, just like `str` methods. It makes perfect sense in a ORM context to support a fluent API (SQLAlchemy being another example, used in my answer). – Martijn Pieters Mar 28 '16 at 12:15
18

It is an excellent idea for APIs where you are building state through methods. SQLAlchemy uses this to great effect for example:

>>> from sqlalchemy.orm import aliased
>>> adalias1 = aliased(Address)
>>> adalias2 = aliased(Address)
>>> for username, email1, email2 in \
...     session.query(User.name, adalias1.email_address, adalias2.email_address).\
...     join(adalias1, User.addresses).\
...     join(adalias2, User.addresses).\
...     filter(adalias1.email_address=='jack@google.com').\
...     filter(adalias2.email_address=='j25@yahoo.com'):
...     print(username, email1, email2)

Note that it doesn't return self in many cases; it will return a clone of the current object with a certain aspect altered. This way you can create divergent chains based of a shared base; base = instance.method1().method2(), then foo = base.method3() and bar = base.method4().

In the above example, the Query object returned by a Query.join() or Query.filter() call is not the same instance, but a new instance with the filter or join applied to it.

It uses a Generative base class to build upon; so rather than return self, the pattern used is:

def method(self):
    clone = self._generate()
    clone.foo = 'bar'
    return clone

which SQLAlchemy further simplified by using a decorator:

def _generative(func):
    @wraps(func)
    def decorator(self, *args, **kw):
        new_self = self._generate()
        func(new_self, *args, **kw)
        return new_self
    return decorator

class FooBar(GenerativeBase):
    @_generative
    def method(self):
        self.foo = 'bar'

All the @_generative-decorated method has to do is make the alterations on the copy, the decorator takes care of producing the copy, binding the method to the copy rather than the original, and returning it to the caller for you.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
12

Here is an example which demonstrates a scenario when it may be a good technique

class A:
    def __init__(self, x):
        self.x = x
    def add(self, y):
        self.x += y
        return self
    def multiply(self, y):
        self.x *= y
        return self
    def get(self):
        return self.x
a = A(0)
print a.add(5).mulitply(2).get()

In this case you are able to create an object in which the order in which operations are performed are strictly determined by the order of the function call, which might make the code more readable (but also longer).

A. White
  • 25
  • 6
Banach Tarski
  • 1,821
  • 17
  • 36
4

If you so desire, you can use a decorator here. It will stand out to someone looking through your code to see the interface, and you don't have to explicitly return self from every function (which could be annoying if you have multiple exit points).

import functools


def fluent(func):
    @functools.wraps(func)
    def wrapped(*args, **kwargs):
        # Assume it's a method.
        self = args[0]
        func(*args, **kwargs)
        return self
    return wrapped


class Foo(object):
    @fluent
    def bar(self):
        print("bar")

    @fluent
    def baz(self, value):
        print("baz: {}".format(value))

foo = Foo()
foo.bar().baz(10)

Prints:

bar
baz: 10
Waleed Khan
  • 11,426
  • 6
  • 39
  • 70