Except operator

I was trying the following list comprehension:

dirpaths = [x for x in datadir.iterdir() if x.is_dir()]

where I got an OSError due to some network access “[WinError 59] An unexpected network error occurred”. As I don’t care about the directories I don’t have access to, I wonder how I could ignore an exception within a list comprehension and avoid having to write a for-loop with a try/except block:

dirpaths = []
for path in datadir.iterdir():
    try:
        is_dir = path.is_dir()
    except OSError:
        continue

    if is_dir:
        dirpaths.append(path)

I tried looking for other ideas in the discussion board, but couldn’t find anything relevant. One idea I had was possibly introducing an except as an inline operator.

dirpaths = [x for x in datadir.iterdir() if x.is_dir() except False]

This would follow a similar pattern as the or operator, but would handle exceptions raised by the preceding expression:

a = 0 or 5
a = foo() except 5

One drawback is that you wouldn’t be able to specify a specific type of exception.

Philippe

Yes

I like this suggestion, it’s similar to the proposal for a with suffix.

Combine them to get significantly more powerful expressions than what we currently have:

import json

data = json.load(fp) \
  with open('data.json') as fp \
  except FileNotFoundError as None

Something like this would also be very nice in my opinion:

import (importlib.resources as resources) \
    except ImportError as (importlib_resources as resources)

However things could get messy if you wanted to be able to bind the resulting exception:

foo() except ((SomeError, AnotherError) as exc) as (handle_error(exc))

Workaround

As a workaround, you can wrap these unavoidable multi-line statements as functions, which can then be slotted into expressions (such as inside a list comprehension) as needed. Your example would then become:

def is_dir(path):
    try:
        return path.is_dir()
    except OSError:
        return False

dirpaths = [x for x in datadir.iterdir() if is_dir(x)]

This would be suitable for most codebases, in my opinion. I use this kind of pattern all the time.


Functional style with callbacks

Another approach is to define somewhat-generic wrappers for with and except that lets you pass a callback, but with the restricted lambda syntax of Python it’s not all that elegant:

from functools import partial

def with_(context, callback):
    with context() as x:
        return callback(x)

def except_(func, exctype, callback):
    try:
        return func()
    except exctype as exc:
        return callback(exc)

data = except_(
    lambda: with_(
        partial(open, 'data.json'),
        json.load),
    FileNotFoundError,
    lambda exc: None)

Your particular example wouldn’t be that bad, however:

from operator import methodcaller

dirpaths = [
    x
    for x in datadir.iterdir()
    if except_(x.is_dir, OSError, lambda exc: False)
]