Exception handling syntax in comprehensions

Rationale

Consider this example:

# choosing a function which has a domain [-1, 1]
func = lambda x: 1 / x

# try to interpolate the coordiates with a dict comprehension
{x: func(x) for x in range(-5, 5)}
# This will generate a "ZeroDivisionError: division by zero"

In this example when x is 0, a ZeroDivisionError is raised and interrupts the comprehension.
We can solve this problem by introducing a new syntax in Python:

{x: func(x) for x in range(-5, 5) except ZeroDivisionError}
# Expected output: {-5: -0.2, -4: -0.25, -3: -0.3333333333333333, -2: -0.5, -1: -1.0, 1: 1.0, 2: 0.5, 3: 0.3333333333333333, 4: 0.25}

This seems to be relatively useless since we could easily work around by adding the filter if x != 0 in the comprehension. But if an arbitrary function is chosen without knowing the domain, then error handling is NEEDED instead of HELPFUL:

import numpy as np
import math

func = eval(input())    # here I put math.asin

# There is *absolutely* no way to do LBYL here, 
# and we should have syntax like this to do EAFP:
{x: func(x) for x in np.linspace(-2, 2, 10) except ValueError}
# Expected output: {-0.6666666666666667: -0.7297276562269664, -0.22222222222222232: -0.22409309230137095, 0.22222222222222232: 0.22409309230137095, 0.6666666666666665: 0.7297276562269661}

Syntax

  1. By simply adding except or except SomeError means the same as continue, example above has the same meaning as the one showing below:
dest = {}
for x in np.linspace(-2, 2, 10):
  try:
    a[x] = func(x)
  except ValueError:
    continue
  1. Adding an expression after handling the error simply means “when the error occurs evaluate this expression and use it as value”:
[1 / x for x in range(-5, 5) if not x % 2 except ZeroDivision "oopsie"]
# Expected Output: [-0.25, -0.5, "oopsie", 0.5, 0.25]

For dicts, the default value should always be a tuple, where the first element is used as the key and second element as the value:

{x: 1 / x for x in range(-5, 5) if not x % 2 except ZeroDivision as e (x, e)}
# Expected Output: {-4: -0.25, -2: -0.5, 0: ZeroDivisionError('division by zero'), 2: 0.5, 4: 0.25}
  1. Multiple exception handling:
    The syntax should allow multiple excepts chaining together just like chaining ifs:
def my_func(x):
  if x == 0:
    raise TypeError
  if x == 2:
    raise ValueError
  return x
  
[my_func(x) for x in range(-2, 4) except TypeError except ValueError "oopsie"]
# expected output: [-2, -1, 1, "oopsie", 3]

It would be great if we have something like this to embrace EAFP and strengthen comprehensions.

I give you PEP 463.

If you have anything new to add to the discussion since the PEP was written, it would be good to revisit it.

3 Likes

Ah I see it there, the general motivation would be more like an enhancement on comprehensions then rather than “embracing EAFP” nor “making e.g. dict.get unnecessary”.
Without the syntax, we will need to populate the collections or creating generators manually, which I think is overly verbose, and comprehensions are generally faster than manually populating.
Would you think there is still a chance for writing a pep out of this? Since PEP 463 was created 8 years ago and the general motivation is fairly different?

I rate this comprehension-specific idea lower than the general idea in PEP 463. new = f(x) for x in collection abbreviates a very common initialized loop pattern that is easily grasped as one thought.

new = []
for x in collection:
    new.append(x)

and similarly for set, dict, and generator comprehensions. if expression corresponds to an added line in the loop which is a fairly common variation, which implements the ‘filter’ rather than or in addition to ‘map’. If one wants to do something more complicated, whether adding exception handling or a multiple statement calculation of the value, I and many others think one should use the more general for-statement rather than stuff more and more into an already overloaded expression.

Edit: I also do not find the examples persuasive. The interpolation dict will fail for values between -1 and +1 when f(0) is sought. One based on a user input will have problems also if there are exceptions.

1 Like

There’s always a chance.

Personally, I think that limiting this feature to only comprehensions is less likely than re-considering the rejection on a general exception expression, but I’m not the decision maker.

If you want to push this:

  • start by describing the case for it here;
  • also mention it on the Python-Ideas mailing list;
  • see what feedback you get;
  • if you get positive feedback, in favour of the idea, ask for a core developer to sponsor the idea.

If no core developer volunteers to sponsor the idea, you are out of luck.

If you get someone who will sponsor it, then you can write a PEP.

You might also see if you can get community feedback on Reddit’s /python subreddit, although that’s not official.

Note that this is not a good time of the year to try to get core developers’ attention. Many of them are very, very busy with work, we are one week away from the big end of year Christmas holiday period. Feel free to have the discussion now, but I would wait until at least mid January before asking for a sponsor.

1 Like