2

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
5
  • After calling might_return_false(), you are trying to invoque the add() method on a object of the class bool (False), which obviously raises an AttributeError Commented May 17, 2020 at 20:43
  • Yes, but I'd like to keep it from returning any AttributeError, and just return False and stop the chain. Like I said, this might not be possible Commented May 17, 2020 at 20:52
  • 1
    That medium article is low on details and not highly rated. I don't understand what they are saying. "super powerful (and somewhat ‘magical’)" sounds like voodoo Commented May 17, 2020 at 20:53
  • The thing described in the article could be handled by returning a unittest.mock.Mock. It doesn't sound like a great strategy TBH; the whole "silent" aspect of it means you're inviting difficult-to-find bugs. Commented May 17, 2020 at 20:56
  • Thanks for the input. I will change the structure of my code and try to deal with the errors in a helpful way. Commented May 17, 2020 at 21:02

2 Answers 2

3

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()
Sign up to request clarification or add additional context in comments.

2 Comments

I appreciate the explanation from the article. I agree with you that it's probably not the way to go for the structure of my script. I don't know which answer I should mark as correct though :P
IMHO, this answer shows up a great understanding of how Python works. +1 for cool stuff!
2

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.

1 Comment

Thanks for answering. I want to keep try/except as a last resort if silent chain failing just isn't possible.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.