136

Is there a way in python to turn a try/except into a single line?

something like...

b = 'some variable'
a = c | b #try statement goes here

Where b is a declared variable and c is not... so c would throw an error and a would become b...

Braiam
  • 1
  • 11
  • 47
  • 78
Brant
  • 5,721
  • 4
  • 36
  • 39

17 Answers17

116

In python3 you can use contextlib.suppress:

from contextlib import suppress

d = {}
with suppress(KeyError): d['foo']
dset0x
  • 1,260
  • 1
  • 10
  • 4
  • 18
    this should be the standard answer – Sphynx-HenryAY Jun 06 '19 at 02:37
  • 5
    this answer don't solve the question. `suppress` just do `pass` on `except:` and here is no way to handle exception – rzlvmp Aug 23 '21 at 07:13
  • 1
    Can we use suppress without giving any exeption type like `KeyError` and apply it for all exceptions? – alper Sep 09 '21 at 09:01
  • 2
    @alper Yes, just use `Exception` if you want to catch generic exceptions like `KeyError` and `IndexError`, but `BaseException` if you want to catch `SystemExit`, `KeyboardInterrupt` and `GeneratorExit` as well (which you mostly don't care about) – LMCuber Sep 10 '21 at 18:44
  • 1
    So I have to use `Exception` and `BaseException` together to cover all the exceptions right? Something like: `with suppress(Exception, BaseException)` ? – alper Sep 11 '21 at 13:25
  • @alper no, Exception is a "child" of BaseException. If you except BaseException you are handling every exception that exists – Lysander12 PT Sep 12 '21 at 18:06
  • @Lysander12PT But I don't see `except BaseException as e:` in most solutions in stackoverflow; instead in all the examples I always see `except Exception as e:`. Why than `Exception ` prefered over `BaseException `? – alper Sep 13 '21 at 12:13
  • 2
    @alper Because `BaseException` also handles `KeyboardInterrupt` and `SystemExit` (exception that is called when using `sys.exit()`), etc. Handling those execeptions would make it impossible for you to exit your program unless you use the task manager or similar to force kill it. Here's a video that explains it very well: https://www.youtube.com/watch?v=zrVfY9SuO64 – Lysander12 PT Sep 14 '21 at 02:48
  • Getting " KeyError: 'foo' " here. – MItrajyoti Oct 21 '21 at 14:47
  • for the above example `d.get('foo')` would be more appropriate – Fernando Wittmann Sep 27 '22 at 15:26
96

This is terribly hackish, but I've used it at the prompt when I wanted to write up a sequence of actions for debugging:

exec "try: some_problematic_thing()\nexcept: problem=sys.exc_info()"
print "The problem is %s" % problem[1]

For the most part, I'm not at all bothered by the no-single-line-try-except restriction, but when I'm just experimenting and I want readline to recall a whole chunk of code at once in the interactive interpreter so that I can adjust it somehow, this little trick comes in handy.

For the actual purpose you are trying to accomplish, you might try locals().get('c', b); ideally it would be better to use a real dictionary instead of the local context, or just assign c to None before running whatever may-or-may-not set it.

Walter Mundt
  • 24,753
  • 5
  • 53
  • 61
77

There is no way to compress a try/except block onto a single line in Python.

Also, it is a bad thing not to know whether a variable exists in Python, like you would in some other dynamic languages. The safer way (and the prevailing style) is to set all variables to something. If they might not get set, set them to None first (or 0 or '' or something if it is more applicable.)


If you do assign all the names you are interested in first, you do have options.

  • The best option is an if statement.

    c = None
    b = [1, 2]
    
    if c is None:
        a = b
    else:
        a = c
    
  • The one-liner option is a conditional expression.

    c = None
    b = [1, 2]
    a = c if c is not None else b
    
  • Some people abuse the short-circuiting behavior of or to do this. This is error prone, so I never use it.

    c = None
    b = [1, 2]
    a = c or b
    

    Consider the following case:

    c = []
    b = [1, 2]
    a = c or b
    

    In this case, a probably should be [], but it is [1, 2] because [] is false in a boolean context. Because there are lots of values that can be false, I don't use the or trick. (This is the same problem people run into when they say if foo: when they mean if foo is not None:.)

NikT
  • 1,590
  • 2
  • 16
  • 29
Mike Graham
  • 73,987
  • 14
  • 101
  • 130
  • Thanks. The problem is that its actually a django model.objects.get query i am trying to test. the .get returns an error if no data is found... it doesn't return None (which annoys me) – Brant Mar 26 '10 at 16:36
  • @Brant, Okay, that situation is a bit different than checking if a variable is set (no variables are declared in Python). The typical style in Python is to prefer raising exceptions to returning errors as values, which many of us actually love. Having to check the return code of an operation every time and having a hard time tracking down errors if I don't is something I definitely don't miss about C when writing Python. In any event, though it's been discussed, there is no one-line syntax for a `try`/`except` block. Luckily lines are cheap, so the 4-line solution should work for you. ;-) – Mike Graham Mar 26 '10 at 16:40
  • It is part of a large set of tuples within a dict... I was just trying to shorten things up a bit – Brant Mar 26 '10 at 16:45
  • 2
    Don't use `get` if you don't want an exception. Use `filter` instead. – jcdyer Mar 26 '10 at 18:03
  • @MikeGraham Good answer - a hint (link?) why the short-circuiting is error prone would be nice. – kratenko Apr 25 '14 at 14:43
  • @kratenko -- check out my example in the post – Mike Graham Apr 26 '14 at 13:32
  • In python3, there is a way. See [answer](https://stackoverflow.com/a/52020518/2476373) of @dset0x below – Yuval Atzmon Sep 10 '19 at 06:45
26

Version of poke53280 answer with limited expected exceptions.

def try_or(func, default=None, expected_exc=(Exception,)):
    try:
        return func()
    except expected_exc:
        return default

and it could be used as

In [2]: try_or(lambda: 1/2, default=float('nan'))
Out[2]: 0.5

In [3]: try_or(lambda: 1/0, default=float('nan'), expected_exc=(ArithmeticError,))
Out[3]: nan

In [4]: try_or(lambda: "1"/0, default=float('nan'), expected_exc=(ArithmeticError,))
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
[your traceback here]
TypeError: unsupported operand type(s) for /: 'str' and 'int'

In [5]: try_or(lambda: "1"/0, default=float('nan'), expected_exc=(ArithmeticError, TypeError))
Out[5]: nan
  • What is the comma in "expected_exc=(Exception,)" for? Can you explain, please? – ibilgen Jul 26 '20 at 15:25
  • 2
    @ibilgen The comma converts the expression to a [tuple](https://docs.python.org/3/tutorial/datastructures.html#tuples-and-sequences). Writing `(Exception)` is equal to leaving out the brackets. `(Exception, )` tells the interpreter that this is a tuple (something like a list) with one entry. In this example this is used so `expected_exc` can be more than one exception. – miile7 Dec 09 '20 at 08:42
  • 1
    My favorite solution! Sometimes I need logging where I have to know a number of variables currently in scope, and I'm not given a stack trace and it's impossible to run the step debugger (like on a remote server). This allows me to see variables of interest in one line, so that I don't have to do a bunch of if statements, I can just use this one line with needed_variable = try_or(lambda : var_maybe_not_in_scope, default="none_present") – Jonathan Ma Feb 05 '21 at 14:14
22

Another way is to define a context manager:

class trialContextManager:
    def __enter__(self): pass
    def __exit__(self, *args): return True
trial = trialContextManager()

Then use the with statement to ignore errors in one single line:

>>> with trial: a = 5      # will be executed normally
>>> with trial: a = 1 / 0  # will be not executed and no exception is raised
>>> print a
5

No exception will be raised in case of a runtime error. It's like a try: without the except:.

bitagoras
  • 221
  • 2
  • 2
  • 3
    This is great! Since there's no explicit try/except, could you explain briefly how the context manager deals with errors? – Patrick Nov 05 '17 at 12:20
  • 1
    @Patrick see https://stackoverflow.com/a/35483461/412529 for how returning `True` from `__exit__` suppresses errors – jnnnnn Oct 11 '22 at 20:14
13

The problem is that its actually a django model.objects.get query i am trying to test. the .get returns an error if no data is found... it doesn't return None (which annoys me)

Use something like this:

print("result:", try_or(lambda: model.objects.get(), '<n/a>'))

Where try_or is an utility function defined by you:

def try_or(fn, default):
    try:
        return fn()
    except:
        return default

Optionally you can restrict the accepted exception types to NameError, AttributeError, etc.

poke53280
  • 456
  • 5
  • 5
  • 2
    Interesting and nice approach! Suggestion: the `lambda` is pointless, just pass `model.objects.get` (without the `()` that performs a call) and it'll work as intended – MestreLion Apr 13 '21 at 01:16
12

How about using two lines. is it ok ?

>>> try: a = 3; b= 0; c = a / b
... except : print('not possible'); print('zero division error')
...
not possible
zero division error
surendra ben
  • 179
  • 1
  • 4
8
parse_float = lambda x, y=exec("def f(s):\n try:\n  return float(s)\n except:  return None"): f(x)

There is always a solution.

Stephen Rauch
  • 47,830
  • 31
  • 106
  • 135
8

Works on Python3, inspired by Walter Mundt

exec("try:some_problematic_thing()\nexcept:pass")

For mulitiples lines into one line

exec("try:\n\tprint('FirstLineOk')\n\tsome_problematic_thing()\n\tprint('ThirdLineNotTriggerd')\nexcept:pass")

Ps: Exec is unsafe to use on data you don't have control over.

Punnerud
  • 7,195
  • 2
  • 54
  • 44
4

You can do it by accessing the namespace dict using vars(), locals(), or globals(), whichever is most appropriate for your situation.

>>> b = 'some variable'
>>> a = vars().get('c', b)
jcdyer
  • 18,616
  • 5
  • 42
  • 49
  • 4
    This doesn't work exactly the same as checking whether a variable is set (though it does if you are interested in a particular scope.) Also, ewwwwwwww..... – Mike Graham Mar 26 '10 at 18:11
2

You mentioned that you're using django. If it makes sense for what you're doing you might want to use:

my_instance, created = MyModel.objects.get_or_create()

created will be True or False. Maybe this will help you.

blokeley
  • 6,726
  • 9
  • 53
  • 75
2

Use with syntax in one line:

class OK(): __init__ = lambda self, *isok: setattr(self, 'isok', isok); __enter__ = lambda self: None; __exit__ = lambda self, exc_type, exc_value, traceback: (True if not self.isok or issubclass(exc_type, self.isok) else None) if exc_type else None

Ignore any errors:

with OK(): 1/0

Ignore specified errors:

with OK(ZeroDivisionError, NameError): 1/0
Sakuya
  • 21
  • 4
1

if you need to actually manage exceptions:
(modified from poke53280's answer)

>>> def try_or(fn, exceptions: dict = {}):
    try:
        return fn()
    except Exception as ei:
        for e in ei.__class__.__mro__[:-1]:
            if e in exceptions: return exceptions[e]()
        else:
            raise


>>> def context():
    return 1 + None

>>> try_or( context, {TypeError: lambda: print('TypeError exception')} )
TypeError exception
>>> 

note that if the exception is not supported, it will raise as expected:

>>> try_or( context, {ValueError: lambda: print('ValueError exception')} )
Traceback (most recent call last):
  File "<pyshell#57>", line 1, in <module>
    try_or( context, {ValueError: lambda: print('ValueError exception')} )
  File "<pyshell#38>", line 3, in try_or
    return fn()
  File "<pyshell#56>", line 2, in context
    return 1 + None
TypeError: unsupported operand type(s) for +: 'int' and 'NoneType'
>>> 

also if Exception is given, it will match anything below.
(BaseException is higher, so it will not match)

>>> try_or( context, {Exception: lambda: print('exception')} )
exception
Tcll
  • 7,140
  • 1
  • 20
  • 23
1

Here is a simpler version of the answer provided by @surendra_ben

a = "apple"
​
try: a.something_that_definitely_doesnt_exist
except: print("nope")

...

nope
Kermit
  • 4,922
  • 4
  • 42
  • 74
0

The two-liner version doesn't work me. I use VSCode on ubuntu 20.04 x64.It only works if I move the exception statements to a new line. The try can remain a single line. So minimally I would need 3 lines. Don't know if this is a bug or feature.

mo FEAR
  • 552
  • 4
  • 8
  • While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. - [From Review](/review/late-answers/30117925) – Xbel Oct 19 '21 at 11:40
0

An actual clean oneliner that reads (somewhat) like the try-except block and is not super dirty would be as follows:

val = (lambda: dictionary["wrong key"]) % cept(KeyError) % (lambda: "default value")

This needs a setup like this

from infix import mod_infix

def cept(*args, as_exc=False):
    if not args:
        args = Exception

    @mod_infix
    def _cept(f1, f2):
        try:
            return f1()
        except args as e:
            if as_exc:
                return f2(e)
            else:
                return f2()
    return _cept

I wish I could do away with the lambdas, but there is no way.

I personally find the rejection notice for https://peps.python.org/pep-0463/ unsatisfying. Guido, if you are reading this (yea, I wish), please let us know.

abc MM
  • 1
0

I see multiple examples here using try..except for handling dictionary keys. In those instances, the appropriate way would be to use the .get method which will return None when it is not available. Example

my_dict = {'a': 1, 'b': 2}
print(my_dict.get('c'))
# Output: None
Fernando Wittmann
  • 1,991
  • 20
  • 16