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.