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.
3 Answers
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 return self. (Note that this has nothing to do with __call()__).
class foo(): def __init__(self, kind=None): self.kind = kind def my_print(self): print (self.kind) return self def line(self): self.kind = 'line' return self def bar(self): self.kind='bar' return self You can use foo objects in a non-chained way by ignoring their returned values:
a = foo() a.line() a.my_print() a.bar() a.my_print() assert a.kind == 'bar' 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:
b = foo() b.line().my_print().bar().my_print() assert b.kind == 'bar' Or even:
c = foo().line().my_print().bar().my_print() assert c.kind == 'bar' 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.)
class foo(): def __init__(self, kind=None): self.kind = kind def my_print(self): print (self.kind) return self @property def line(self): self.kind = 'line' return self @property def bar(self): self.kind='bar' return self a = foo() a.line a.my_print() a.bar a.my_print() assert a.kind == 'bar' b = foo() b.line.my_print().bar.my_print() assert b.kind == 'bar' c = foo().line.my_print().bar.my_print() assert c.kind == 'bar' 3Use properties (descriptors).
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) @property def line(self): return self(kind='line') @property def bar(self): return self(kind='bar') 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.
ther's an another interesting way of achieving this
class Foo: def __init__(self, kind=[]): self.kind = kind def __getattr__(self, attrs): self.attrs = attrs return Foo(self.kind + [attrs]) def __call__(self): return self.kind[::-1][0] my_obj = Foo() print(my_obj.bar.line.bar.bar.line()) with this code u don't have to pass .my_print() but one thing to note here is the Foo class will take anything as argument like if we try print(my_obj.bar.line.bar.bar.circle()) it will return circle.
You can also edit this code to take the args while calling any function.