Function Chaining in Python, ignore rest of chain if a segment returns False

Viewed 341

The title pretty much explains the problem. I don't know if there's a practical solution to this or if I'm being too picky over the behavior of my code. This article was hinting in the right direction, but I never got any code to work. https://medium.com/@adamshort/python-gems-5-silent-function-chaining-a6501b3ef07e

Here's an example of the functionality that I want:

class Calc:
    def __init__(self, n=0):
        self.n = n

    def add(self, n):
        self.n += n
        return self

    def might_return_false(self):
        return False

    def print(self):
        print(self.n)
        return self


w = Calc()

# The rest of the chain after might_return_false should be ignored
addedTwice = w.add(5).might_return_false().add(5).print()

w.print() # Should print 5

print(addedTwice) # Should print False
2 Answers

I think the article meant something more or less like below (but I prefer the other answer using exception, as it's more readable and better testable). Create a helper class:

class Empty:
    def __call__(self, *args, **kwargs):
        return self
    def __getattr__(self, *args, **kwargs):
        return self
    def print(self, *args, **kwargs):
        return False

and

def might_return_false(self):
    return Empty()

Exceptions are a great way to interrupt a chained operation:

class CalcError(Exception):
    pass


class Calc:
    def __init__(self, n: int = 0):
        self.n = n

    def add(self, n: int) -> 'Calc':
        self.n += n
        return self

    def might_raise(self) -> 'Calc':
        raise CalcError

    def __str__(self) -> str:
        return str(self.n)


w = Calc()

try:
    w.add(5).might_raise().add(5)
    addedTwice = True
except CalcError:
    addedTwice = False

print(w)           # prints 5
print(addedTwice)  # prints False

You could also do chains like:

w = Calc()
num_added = 0
try:
    w.add(5)
    num_added += 1
    w.add(5)
    num_added += 1
    w.might_raise()
    w.add(5)
    num_added += 1
    w.add(5)
    num_added += 1
except CalcError:
    print(f"Stopped after {num_added} additions")

If you attempt to do this with return instead of raise, you need to check the status at each step of the chain so that you can switch off to some other branch of code (probably via an if block). Raising an exception has the very useful property of immediately interrupting execution, no matter where you are, and taking you straight to the nearest matching except.

Related