Вдохновлённый недавним постом про текучие интерфейсы на 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% прироста в скорости), мы ещё проводим необходимые проверки не на каждом считывании полей объекта, а на каждой записи (что происходит реже). Если запись отсутствует — работает так же быстро как и способ с декораторами.
