Is there a more elegant way to filter the failed results of a function?
It seems like you have control of f
and can modify how it handles errors.
If that's the case, and None
isn't a valid output for the function, I would have it return None
on an error instead of throwing:
def f(x):
if x == 5: return None
else: return 2*x
Then filter it:
results = (f(x) for x in interesting_values) # A generator expression; almost a list comptehension
valid_results = filter(lambda x: x is not None, results)
This is a stripped down version of what's often referred to as the "Optional Pattern". Return a special sentinal value on error (None
in this case), else, return a valid value. Normally the Optional type is a special type and the sentinal value is a subclass of that type (or something similar), but that's not necessary here.
I'm going to assume here that you have no control over the source of f
. If you do, the first suggestion is to simply rewrite f
not to throw exceptions, as it's clear that you are expecting that execution path to occur, which by definition makes it not exceptional. However, if you don't have control over it, read on.
If you have a function that might fail and want its "failure" to be ignored, you can always just wrap the function
def safe_f(x):
try:
return f(x)
except ValueError:
return None
result = filter(lambda x: x is not None, map(safe_f, values))
Of course, if f
could return None
in some situation, you'll have to use a different sentinel value. If all else fails, you could always go the route of defining your own _sentinel = object()
and comparing against it.
You could add another layer on top of your function. A decorator if you will, to transform the exception into something more usable. Actually this is a function that returns a decorator, so two additional layers:
from functools import wraps
def transform(sentinel=None, err_type=ValueError):
def decorator(f):
@wraps(f)
def func(*args, **kwargs):
try:
return f(*args, **kwargs)
except err_type:
return sentinel
return func
return decorator
@transform()
def f(...): ...
interesting = range(10)
result = [y for y in (f(x) for x in interesting) if y is not None]
This solution is tailored for the case where you get f
from somewhere else. You can adjust transform
to return a decorator for a given set of exceptions, and a sentinel value other than None
, in case that's a valid return value. For example, if you import f
, and it can raise TypeError
in addition to ValueError
, it would look like this:
from mystuff import f, interesting
sentinel = object()
f = transform(sentinel, (ValueError, TypeError))(f)
result = [y for y in (f(x) for x in interesting) if y is not sentinel]
You could also use the functional version of the comprehension elements:
result = list(filter(sentinel.__ne__, map(f, interesting)))