Search code examples
pythonmethod-chaining

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.


Solution

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