Вдохновлённый недавним постом про текучие интерфейсы на PHP, я сразу задумался как можно реализовать подобное на питоне проще и красивее (на питоне всегда всё проще и красивее). Предлагаю несколько способов в порядке поступления мыслей.

Для построении цепочки операторов нам необходимо чтобы функция возвращала экземпляр класса. Это можно вручную задать.
Очевидно что такой подход работает идеально, но мы ищем немножко другое.
Это первая идея, пришедшая в голову. Определяем декоратор для методов в классе. Нам очень на руку что экземпляр передаётся первым аргументом.
Просто помечаем декоратором функции, которые нужно использовать в цепочке. Возвращаемое значение игнорируется и вместо него передаётся экземпляр класса.
Способ явно читабельнее — сразу видно какие функции можно использовать в цепочке. Однако, обычно архитектурный подход распространяется на большинство методов класса, которые помечать по одному не интересно.
Мы можем при вызове функции проверять возвращаемое значение. При отсутствии оного мы передаём сам объект. Делать это будем через __getattribute__, перехватывающего любой доступ к методам и полям класса. Для начала просто определим класс с подобным поведением, все рабочие классы будем наследовать от него.
Если метод возвращает значение — оно передаётся. Если нет — то вместо него идёт сам экземпляр класса.
Теперь нам не надо никак модифицировать рабочий класс кроме указания класса-цепочки как одного из родительских. Очевидный недостаток — мы не можем использовать __getattribute__ в своих целях.
Мы можем использовать метакласс для организации нужной обёртки рабочего класса. При инициализации последнего мы будем на лету обёртывать __getattribute__ (причём его отсутствие нам тоже не мешает).
От предыдущего варианта практически ничем не отличается — мы только контролируем создание __getattribute__ с помощью метакласса. Для обёртки рабочего класса достаточно указать __metaclass__.
Как видно, имеющийся изначально __getattribute__ в рабочем классе работает. При наследовании от рабочего класса поведение сохраняется — __getattribute__ тоже наследуется. Если родной __getattribute__ не возвращает ничего (даже AttributeError), то мы тоже возвращаем сам объект.
Хотя повсеместное применение текучих интерфейсов сомнительно, но всё же есть случаи когда подобные структуры будут уместны. Скажем, обработка изображений или любых сущностей, над которыми последовательно проводится множество операций.
PS Последний вариант, по моему мнению, не содержит очевидных недостатков. Если кто может предложить лучший вариант — буду рад выслушать, равно как и указания недостатки моего.
PPS По просьбам трудящихся ссылки на упомянутую статью и описание в википедии
Update
Тов. davinchi справедливо указал что обёртывать на каждом вызове по меньшей мере странно. Плюсом к этому, мы при каждом обращении к полям объекта прогоняем проверку.
Теперь мы обработаем все методы сразу, но будем проверять модификацию и создание методов для того чтобы и их обернуть.
Помимо того что мы теперь при каждом вызове не оборачиваем методы (что дало ~30% прироста в скорости), мы ещё проводим необходимые проверки не на каждом считывании полей объекта, а на каждой записи (что происходит реже). Если запись отсутствует — работает так же быстро как и способ с декораторами.

Способ первый — в лоб
Для построении цепочки операторов нам необходимо чтобы функция возвращала экземпляр класса. Это можно вручную задать.
def add(self,x):
self.val += x
return self
Очевидно что такой подход работает идеально, но мы ищем немножко другое.
Способ второй — декораторы
Это первая идея, пришедшая в голову. Определяем декоратор для методов в классе. Нам очень на руку что экземпляр передаётся первым аргументом.
def chained(fn):
def new(*args,**kwargs):
fn(*args,**kwargs)
return args[0]
return new
class UsefulClass1():
def __init__(self,val): self.val = val
@chained
def add(self,val): self.val += val
@chained
def mul(self,val): self.val *= val
Просто помечаем декоратором функции, которые нужно использовать в цепочке. Возвращаемое значение игнорируется и вместо него передаётся экземпляр класса.
>>> print UsefulClass1(10).add(5).mul(10).add(1).val
151
Способ явно читабельнее — сразу видно какие функции можно использовать в цепочке. Однако, обычно архитектурный подход распространяется на большинство методов класса, которые помечать по одному не интересно.
Способ третий — автоматический
Мы можем при вызове функции проверять возвращаемое значение. При отсутствии оного мы передаём сам объект. Делать это будем через __getattribute__, перехватывающего любой доступ к методам и полям класса. Для начала просто определим класс с подобным поведением, все рабочие классы будем наследовать от него.
from types import MethodType
class Chain(object):
def __getattribute__(self,item):
fn = object.__getattribute__(self,item)
if fn and type(fn)==MethodType:
def chained(*args,**kwargs):
ans = fn(*args,**kwargs)
return ans if ans!=None else self
return chained
return fn
class UsefulClass2(Chain):
val = 1
def add(self,val): self.val += val
def mul(self,val): self.val *= val
def third(self): return 386
Если метод возвращает значение — оно передаётся. Если нет — то вместо него идёт сам экземпляр класса.
>>> print UsefulClass2().add(15).mul(16).add(-5).val
251
>>> print UsefulClass2().third()
386
Теперь нам не надо никак модифицировать рабочий класс кроме указания класса-цепочки как одного из родительских. Очевидный недостаток — мы не можем использовать __getattribute__ в своих целях.
Способ четвёртый — Im So Meta...
Мы можем использовать метакласс для организации нужной обёртки рабочего класса. При инициализации последнего мы будем на лету обёртывать __getattribute__ (причём его отсутствие нам тоже не мешает).
from types import MethodType
class MetaChain(type):
def __new__(cls,name,bases,dict):
old = dict.get('__getattribute__',object.__getattribute__)
def new_getattribute(inst,val):
attr = old(inst,val)
if attr==None: return inst
if attr and type(attr)==MethodType:
def new(*args,**kwargs):
ans = attr(*args,**kwargs)
return ans if ans!=None else inst
return new
return attr
dict['__getattribute__'] = new_getattribute
return type.__new__(cls,name,bases,dict)
class UsefulClass3():
__metaclass__ = MetaChain
def __getattribute__(self,item):
if item=="dp": return 493
return object.__getattribute__(self,item)
val = 1
def add(self,val): self.val += val
def mul(self,val): self.val *= val
От предыдущего варианта практически ничем не отличается — мы только контролируем создание __getattribute__ с помощью метакласса. Для обёртки рабочего класса достаточно указать __metaclass__.
>>> print UsefulClass3().dp
493
>>> print UsefulClass3().add(4).mul(5).add(1).mul(25).add(-1).val
649
Как видно, имеющийся изначально __getattribute__ в рабочем классе работает. При наследовании от рабочего класса поведение сохраняется — __getattribute__ тоже наследуется. Если родной __getattribute__ не возвращает ничего (даже AttributeError), то мы тоже возвращаем сам объект.
Вместо заключения
Хотя повсеместное применение текучих интерфейсов сомнительно, но всё же есть случаи когда подобные структуры будут уместны. Скажем, обработка изображений или любых сущностей, над которыми последовательно проводится множество операций.
PS Последний вариант, по моему мнению, не содержит очевидных недостатков. Если кто может предложить лучший вариант — буду рад выслушать, равно как и указания недостатки моего.
PPS По просьбам трудящихся ссылки на упомянутую статью и описание в википедии
Update
Способ пятый — жаркое и очаг
Тов. davinchi справедливо указал что обёртывать на каждом вызове по меньшей мере странно. Плюсом к этому, мы при каждом обращении к полям объекта прогоняем проверку.
Теперь мы обработаем все методы сразу, но будем проверять модификацию и создание методов для того чтобы и их обернуть.
class NewMetaChain(type):
def __new__(cls,name,bases,dict):
old = dict.get('__setattr__',object.__setattr__)
def wrap(fn,inst=None):
def new(*args,**kwargs):
ans = fn(*args,**kwargs)
return ans if ans!=None else inst or args[0]
return new
special = dir(cls)
for item, fn in dict.items():
if item not in special and isinstance(fn,FunctionType):
dict[item] = wrap(fn)
def new_setattr(inst,item,val):
if isinstance(val,FunctionType):
val = wrap(val,inst)
return old(inst,item,val)
dict['__setattr__'] = new_setattr
return type.__new__(cls,name,bases,dict)
class UsefulClass4():
__metaclass__ = NewMetaChain
def __setattr__(self,item,val):
if val == 172: val = "giza"
object.__setattr__(self, item, val)
val = 1
def add(self,val): self.val += val
def mul(self,val): self.val *= val
def nul(self): pass
Помимо того что мы теперь при каждом вызове не оборачиваем методы (что дало ~30% прироста в скорости), мы ещё проводим необходимые проверки не на каждом считывании полей объекта, а на каждой записи (что происходит реже). Если запись отсутствует — работает так же быстро как и способ с декораторами.