0

I want to apply a function in a list comprehension that does not "work" for certain values. A simple example would be [1/x for x in [-2, -1, 0, 1, 2]]. I would like to write something that skips the 0 and returns the following list: [-0.5, -1.0, 1.0, 0.5].

My best shot so far was wrapping the division in a try statement:

def try_div(x):
    try:
        return 1/x
    except ZeroDivisionError:
        pass

result_with_none = [try_div(x) for x in [-2, -1, 0, 1, 2]]
result = [x for x in result_with_none if x is not None]

This seems a bit inconvenient. Can I rewrite try_div in a way that makes the list comprehension skip the 0 element?

Remark: In this simple example, I could of course write [try_div for x in [-2, -1, 0, 1, 2] if x != 0]. This is not practicable in my actual use case, because it is not easy to check a priori which values will raise an exception.

Remark 2: In contrast to this question, I am fine with explicitly handling exceptions in a function (like try_div). My question is mostly about how I could combine the last two steps (result_with_none = ... and result = ...) into one.

Elias Strehle
  • 1,722
  • 1
  • 21
  • 34
  • What is inconvinient in your code? Looks classic (perhaps switch comprehension with generator and use filter as well, but that's small stuff). – kabanus Jul 19 '18 at 08:47
  • Rather, could you clarify what you consider "convenient"? – kabanus Jul 19 '18 at 08:48
  • Looks good to me. @kabanus stated the further possible ameliorations. – Mathieu Jul 19 '18 at 08:49
  • Introducing `None` values just to remove them in the next line feels complicated. I thought there might be a way to skip these elements directly. – Elias Strehle Jul 19 '18 at 08:52
  • 1
    Possible duplicate of [How can I handle exceptions in a list comprehension in Python?](https://stackoverflow.com/questions/1528237/how-can-i-handle-exceptions-in-a-list-comprehension-in-python) – Dumitru Jul 19 '18 at 09:02

1 Answers1

1

I think your first solution is clear, and that's the most important thing in python. Alternatively, how about a generator instead of a comprehension?

def try_div_itr(itr):
    for elem in itr:
        try:
             yield 1 / elem
        except ZeroDivisionError:
             pass

result = list(try_div_itr([-2, -1, 0, 1, 2]))

You could even generalise this

def try_itr(func, itr, *exceptions, **kwargs):
    for elem in itr:
        try:
            yield func(elem, **kwargs)
        except exceptions:
            pass

x = [random.choice([0, 1]) for _ in range(100_000)]

%timeit [i for i in (try_div(i) for i in x) if i is not None]
42.6 ms ± 109 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

%timeit [i for i in [try_div(i) for i in x] if i is not None]
36.3 ms ± 154 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

%timeit list(try_div_itr(x))
25.3 ms ± 85.1 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

%timeit list(try_itr(lambda i: 1/i, x, ZeroDivisionError))  
34.7 ms ± 113 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
# but expect generic method to be slower anyway

%timeit list(try_itr((1).__truediv__, x, ZeroDivisionError))
28.7 ms ± 118 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
# remove lambda abstraction for speed
FHTMitchell
  • 11,793
  • 2
  • 35
  • 47