Basic method chaining

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.

0

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' 
3

Use 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.

Your Answer

Sign up or log in

Sign up using Google Sign up using Facebook Sign up using Email and Password

Post as a guest

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

You Might Also Like