Kalev Maricq
Kalev Maricq

Reputation: 617

Python Define IFERROR function

I'm trying to define my own IFERROR function in python like in Excel. (Yes, I know I can write try/except. I'm just trying to create an inline shorthand for a try/except pattern I often use.) The current use case is trying to get several attributes of some remote tables. The module used to connect to them gives a variety of errors and if that happens, I simply want to record that an error was hit when attempting to get that attribute.

What I've tried: A search revealed a number of threads, the most helpful of which were:

Frequently repeated try/except in Python

Python: try-except as an Expression?

After reading these threads, I tried writing the following:

>>> def iferror(success, failure, *exceptions):
...     try:
...         return success
...     except exceptions or Exception:
...         return failure
...
>>> iferror(1/0,0)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero

I also tried using a context manager (new to me):

>>> from contextlib import contextmanager as cm
>>> @cm
... def iferror(failure, *exceptions):
...     try:
...         yield
...     except exceptions or Exception:
...         return failure
...
>>> with iferror(0,ZeroDivisionError) as x:
...     x=1/0
...
>>> print(x)
None

Is there a way to define a function which will perform a predefined try/except pattern like IFERROR?

Upvotes: 5

Views: 13143

Answers (2)

melpomene
melpomene

Reputation: 85767

The problem with iferror(1/0,0) is that function arguments are evaluated before the function is entered (this is the case in most programming languages, the one big exception being Haskell). No matter what iferror does, 1/0 runs first and throws an error.

We must somehow delay the evaluation of 1/0 so it happens inside the function, in the context of a try block. One way is to use a string (iferror('1/0', 1)) that iferror can then eval. But eval should be avoided where possible, and there is a more light-weight alternative: Function bodies are not evaluated until the function is called, so we can just wrap our expression in a function and pass that:

def iferror(success, failure, *exceptions):
    try:
        return success()
    except exceptions or Exception:
        return failure

def my_expr():
    return 1/0

print(iferror(my_expr, 42))
42

The crucial part here is that we don't call my_expr directly. We pass it as a function into iferror, which then invokes success(), which ends up executing return 1/0.

The only problem is that we had to pull the function argument (1/0) out of the normal flow of code and into a separate function definition, which we had to give a name (even thought it's only used once).

These shortcomings can be avoided by using lambda, which lets us define single-expression functions inline:

def iferror(success, failure, *exceptions):
    try:
        return success()
        #             ^^
    except exceptions or Exception:
        return failure

print(iferror(lambda: 1/0, 42))
#             ^^^^^^^
42

[Live demo]

Compared to your original attempt, only two changes were necessary: Wrap the expression in a lambda:, which delays evaluation, and use () in try: return success() to call the lambda, which triggers evaluation of the function body.

Upvotes: 6

TornaxO7
TornaxO7

Reputation: 1299

Well I found a way but I'm not quite sure, if it's that what you are looking for.
First of all the error occurs if you call the function, as a result your function doesn't start at all! So I gave the first parameter of the function a string.
So the function gets your "test" and "controls" it with eval(). Here is my Code:

def iferror(success: str, failure, *exceptions):
    try:
        # Test
        return eval(success)

    except exceptions or Exception:
        return failure    

iferror("1/0", "Hi there!")

I hope I could hope you.

Upvotes: 2

Related Questions