Reputation: 617
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
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
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
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