29

In Python, it is possible to use one-liners to set values with special conditions (such as defaults or conditions) in a simple, intuitive way.

result = 0 or "Does not exist."  # "Does not exist."

result = "Found user!" if user in user_list else "User not found."

Is it possible to write a similar statement that catches exceptions?

from json import loads

result = loads('{"value": true}') or "Oh no, explosions occurred!"
# {'value': True}

result = loads(None) or "Oh no, explosions occurred!"
# "Oh no, explosions occurred!" is desired, but a TypeError is raised.
2Cubed
  • 3,401
  • 7
  • 23
  • 40
  • 2
    Why not put the standard `try..except` into the function? – TigerhawkT3 Apr 16 '16 at 23:46
  • @Slayer Interestingly, Python will actually use a string (or any other object, in fact) as an "alternate" for `or`. `0 or "Does not exist."` will return `"Does not exist."`. (I have tested it.) – 2Cubed Apr 16 '16 at 23:48
  • @TigerhawkT3 I do not have access to the internals of the `divide_one_by` function - assume that it is defined in a separate module which I am using as a dependency. – 2Cubed Apr 16 '16 at 23:49
  • @2Cubed: no reason to be surprised. `0 OR x` evaluates to `x` for all values of `x`, except`0` itself. – Jongware Apr 16 '16 at 23:49
  • So write a wrapper function, perhaps? – TigerhawkT3 Apr 16 '16 at 23:49
  • @RadLexus - It evaluates to `x` when `x` is `0` as well. It'll simply be `0`. – TigerhawkT3 Apr 16 '16 at 23:50
  • @TigerhawkT3: does that not evaluate to `False` instead? (Testing..) Oh I see it indeed does not. :) `print 0 or False` *does*, though, proving my point on *anything* for `x`. Even 0. – Jongware Apr 16 '16 at 23:52
  • @TigerhawkT3: Good idea. Thanks! – 2Cubed Apr 16 '16 at 23:53
  • 3
    There is a [PEP](http://legacy.python.org/dev/peps/pep-0463/#proposal) to add something like this, but it hasn't been accepted yet. – chepner Apr 17 '16 at 00:26
  • suppress maybe useful for some cases https://stackoverflow.com/a/52020518/8583496 – Alexey Shrub Dec 20 '19 at 07:42

4 Answers4

39

It is not possible to do a one-line exception-handling statement in python. One could write a function to do this.

def safe_execute(default, exception, function, *args):
    try:
        return function(*args)
    except exception:
        return default

Example usage:

from json import loads
safe_execute("Oh no, explosions occurred!", TypeError, loads, None)
# Returns "Oh no, explosions occurred!"
safe_execute("Huh?", TypeError, int, "10")
#Returns 10

Multiple arguments are supported

from operator import div
safe_execute(
    "Divsion by zero is invalid.",
    ZeroDivisionError,
    div, 1, 0
)
# Returns "Divsion by zero is invalid."

safe_execute(
    "Divsion by zero is invalid.",
    ZeroDivisionError,
    div, 1, 1
)
# Returns 1.

The error-catching process may still be interrupted:

from time import sleep
safe_execute(
    "Panic!",
    Exception,
    sleep, 8
)
# Ctrl-c will raise a KeyboardInterrupt

from sys import exit
safe_execute("Failed to exit!", Exception, exit)
# Exits the Python interpreter

If this behavior is undesired, use BaseException:

from time import sleep
safe_execute("interrupted",
             BaseException,
             sleep, 8)
#Pressing Ctrl-c will return "interrupted"
from sys import exit
safe_execute("Naughty little program!",
             BaseException,
             exit)
#Returns "Naughty little program!"
mx0
  • 6,445
  • 12
  • 49
  • 54
pppery
  • 3,731
  • 22
  • 33
  • 46
  • You have a great suggestion. I am trying to create a similar workaround for my problem but the code is unable to work. I have detailed the problem at https://stackoverflow.com/q/56916092/6701627. I will be thankful if you could check the problem and provide some suggestion on it. – thepunitsingh Jul 06 '19 at 17:03
3

It is possible in one line using exec:

parse_float = lambda x, y=exec("def f(s):\n try:\n  return float(s)\n except:  return None"): f(x)
Stephen Rauch
  • 47,830
  • 31
  • 106
  • 135
  • 8
    yeah but that's so horribly unpythonic... it's like putting an image of a website on a website instead of the actual html itself – sp4c38 Jun 22 '21 at 22:01
3

You can use contextlib to suppress exceptions. If you like to live dangerously you could suppress BaseException, which would suppress all builtin exceptions (probably a bad idea). Or you could pick a safe one relevant to your code, like TypeError.

examples:

from contextlib import suppress

# this will execute right along
with suppress(BaseException): fhasldjkfhsa345315

# even raising an Exception will fly just fine
with suppress(BaseException): raise NameError

# correct code will execute just fine
x=5
with suppress(BaseException): x+=2
print(x) # prints 7

# and in your example:
from json import loads
pleasure = suppress(TypeError) # so each line rolls off the tongue :)

with pleasure: result = loads('{"value": True}')
print(result) # prints {'value': True}

with pleasure: result = loads(None)
print(result) # prints {'value': True} because result never changed
Fnord
  • 5,365
  • 4
  • 31
  • 48
1

pppery's answer is a more complete solution, but I prefer a more terse (and simpler) approach:

def maybe(function):
    try:
        return function()
    except:
        return None

With this utility, we can do stuff like this:

# Instead of doing this
last_item = self.stock.items[-1] if self.stock and self.stock.items else None

# It can be done like this
last_item = maybe(lambda: self.stock.items[-1])

Now, for cases when the default value isn't None (like on the question's example), we can add the OR operator safely:

result = maybe(lambda: loads('{"value": true}')) or 'Oh no, explosions occurred!'

It's a readable and easy solution to handle expected exceptions. I wish Python had something similar natively. As Fnord noted in his answer, the closest solution Python has is contextlib.suppress, but it has 2 limitations:

  1. It forces you to specify the exception type (safe, but usually unnecessary).
  2. It doesn't resolve to a value (since the with statement has no return), which prevents you from using the result inside another expression. Notice that using suppress forces you to initialize result with the default string in a separate line.
Leandro 86
  • 157
  • 1
  • 9