I found this method chaining in python, but even with it I couldn't understand method chaining in Python.
Here the goals are two: solve the coding problem and understand method chaining (given that I am still not 100% confident with callables).
Down to the problem definition.
I want a class that has two methods: one sets a parameter of the object = 'line' and the other overwrites to 'bar'.
This is what I got so far:
class foo():
def __init__(self, kind=None):
self.kind = kind
def __call__(self, kind=None):
return foo(kind=kind)
def my_print(self):
print (self.kind)
def line(self):
return self(kind='line')
def bar(self):
return self(kind='bar')
Sadly, with this code I can achieve my goal doing this
a = foo()
a.bar().line().bar().bar().line().my_print()
But I would like to obtain the same result by writing this code
a = foo()
a.bar.line.bar.bar.line.my_print()
How do I achieve this? I guess is something wrong in how I defined the __call__
method. Thanks in advance for your help.
Use properties (descriptors).
Note, though, that you overwrite nothing, the modification doesn't work inplace (which is arguably good, btw). Anyway, this doesn't look like a good design choice for most real-world cases, because at some point your methods will require arguments.
Method chaining is simply being able to add
.second_func()
to whatever.first_func()
returns. It is fairly easily implemented by ensuring that all chainable methods returnself
. (Note that this has nothing to do with__call()__
).You can use
foo
objects in a non-chained way by ignoring their returned values:Or, since every function now returns the object itself, you can operate directly on the returned value. You can use method chaining with this equivalent code:
Or even:
The question of getting rid of the
()
calling syntax is a completely separate concept from method chaining. If you want chain properties, and have those properties mutate their object, use the@property
decorator. (But mutating objects via a property seems dangerous. Better to use a method and name it with a verb:.set_line()
instead of.line
, for example.)