Reputation: 17173
(not to be confused with itertools.chain)
I was reading the following: http://en.wikipedia.org/wiki/Method_chaining
My question is: what is the best way to implement method chaining in python?
Here is my attempt:
class chain():
def __init__(self, my_object):
self.o = my_object
def __getattr__(self, attr):
x = getattr(self.o, attr)
if hasattr(x, '__call__'):
method = x
return lambda *args: self if method(*args) is None else method(*args)
else:
prop = x
return prop
list_ = chain([1, 2, 3, 0])
print list_.extend([9, 5]).sort().reverse()
"""
C:\Python27\python.exe C:/Users/Robert/PycharmProjects/contests/sof.py
[9, 5, 3, 2, 1, 0]
"""
One problem is if calling method(*args)
modifies self.o
but doesn't return None
. (then should I return self
or return what method(*args)
returns).
Does anyone have better ways of implementing chaining? There are probably many ways to do it.
Should I just assume a method always returns None
so I may always return self.o
?
Upvotes: 29
Views: 31729
Reputation: 2713
Caveat: This only works on class
methods() that do not intend to return any data.
I was looking for something similar for chaining Class
functions and found no good answer, so here is what I did and thought was a very simple way of chaining: Simply return the self
object.
So here is my setup:
class Car:
def __init__(self, name=None):
self.name = name
self.mode = 'init'
def set_name(self, name):
self.name = name
return self
def drive(self):
self.mode = 'drive'
return self
And now I can name the car and put it in drive state by calling:
my_car = Car()
my_car.set_name('Porche').drive()
Hope this helps!
Upvotes: 13
Reputation: 9748
What about
def apply(data, *fns):
return data.__class__(map(fns[-1], apply(data, *fns[:-1]))) if fns else data
>>> print(
... apply(
... [1,2,3],
... str,
... lambda x: {'1': 'one', '2': 'two', '3': 'three'}[x],
... str.upper))
['ONE', 'TWO', 'THREE']
>>>
?
.. even keeps the type :)
Upvotes: 3
Reputation: 22659
There is a very handy Pipe
library which may be the answer to your question. For example::
seq = fib() | take_while(lambda x: x < 1000000) \
| where(lambda x: x % 2) \
| select(lambda x: x * x) \
| sum()
Upvotes: 27
Reputation: 5353
It's possible if you use only pure functions so that methods don't modify self.data
directly, but instead return the modified version. You also have to return Chainable
instances.
Here's an example using collection pipelining with lists:
import itertools
try:
import builtins
except ImportError:
import __builtin__ as builtins
class Chainable(object):
def __init__(self, data, method=None):
self.data = data
self.method = method
def __getattr__(self, name):
try:
method = getattr(self.data, name)
except AttributeError:
try:
method = getattr(builtins, name)
except AttributeError:
method = getattr(itertools, name)
return Chainable(self.data, method)
def __call__(self, *args, **kwargs):
try:
return Chainable(list(self.method(self.data, *args, **kwargs)))
except TypeError:
return Chainable(list(self.method(args[0], self.data, **kwargs)))
Use it like this:
chainable_list = Chainable([3, 1, 2, 0])
(chainable_list
.chain([11,8,6,7,9,4,5])
.sorted()
.reversed()
.ifilter(lambda x: x%2)
.islice(3)
.data)
>> [11, 9, 7]
Note that .chain
refers to itertools.chain
and not the OP's chain
.
Upvotes: 18
Reputation: 251355
There isn't going to be any general way of allowing any method of any object to be chained, since you can't know what sort of value that method returns and why without knowing how that particular method works. Methods might return None
for any reason; it doesn't always mean the method has modified the object. Likewise, methods that do return a value still might not return a value that can be chained. There's no way to chain a method like list.index
: fakeList.index(1).sort()
can't have much hope of working, because the whole point of index
is it returns a number, and that number means something, and can't be ignored just to chain on the original object.
If you're just fiddling around with Python's builtin types to chain certain specific methods (like sort and remove), you're better off just wrapping those particular methods explicitly (by overriding them in your wrapper class), instead of trying to do a general mechanism with __getattr__
.
Upvotes: 4