Error handling in context managers

Steve D'Aprano steve+python at pearwood.info
Mon Jan 16 14:07:55 EST 2017


On Tue, 17 Jan 2017 05:06 am, Israel Brewster wrote:

> I generally use context managers for my SQL database connections, so I can
> just write code like:
> 
> with psql_cursor() as cursor:
>     <do whatever>
> 
> And the context manager takes care of making a connection (or getting a
> connection from a pool, more likely), and cleaning up after the fact (such
> as putting the connection back in the pool), even if something goes wrong.
> Simple, elegant, and works well.
> 
> The problem is that, from time to time, I can't get a connection, the
> result being that cursor is None, 

Seriously? psql_cursor().__enter__ returns None instead of failing? That
sounds like a poor design to me.

Where is this psql_cursor coming from?


> and attempting to use it results in an 
> AttributeError. So my instinctive reaction is to wrap the potentially
> offending code in a try block, such that if I get that AttributeError I
> can decide how I want to handle the "no connection" case. This, of course,
> results in code like:
> 
> try:
> with psql_cursor() as cursor:
> <do whatever>
> except AttributeError as e:
> <handle no-connection case>

Except that isn't necessarily the no-connection case. It could be *any*
AttributeError anywhere in the entire with block.

> I could also wrap the code within the context manager in an if block
> checking for if cursor is not None, but while perhaps a bit clearer as to
> the purpose, now I've got an extra check that will not be needed most of
> the time (albeit a quite inexpensive check).

It's cheap, it's only needed once (at the start of the block), it isn't
subject to capturing the wrong exception... I would definitely write:

with psql_cursor() as cursor:
    if cursor is not None:
        <do whatever>


> The difficulty I have with either of these solutions, however, is that
> they feel ugly to me - and wrapping the context manager in a try block
> almost seems to defeat the purpose of the context manager in the first
> place - If I'm going to be catching errors anyway, why not just do the
> cleanup there rather than hiding it in the context manager?

Context managers don't necessarily swallow exceptions (although they can).
That's not what they're for. Context managers are intended to avoid:

try:
    ...
finally:
    ...


*not* try...except blocks. If you need a try...except, then you could avoid
using the context manager and re-invent the wheel:

try:
    ...
except:
    ...
finally:
    # do whatever cleanup the context manager already defines
    # but now you have to do it yourself



or you can let the context manager do what it does, and write your own code
to do what you do:

try:
    with ...:
       ...
except:
    ...



[...]
> says "there should be a better way", so I figured I'd ask: *is* there a
> better way? Perhaps some way I could handle the error internally to the
> context manager, such that it just dumps me back out? 

That's what's supposed to happen:

py> with open('foobarbaz') as f:
...     pass
...
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
FileNotFoundError: [Errno 2] No such file or directory: 'foobarbaz'


Notice that the context manager simply raises an exception on failure, which
I can catch or not as I so choose, rather than returning None.

I really think that the problem here is the design of psql_cursor().


> Of course, that 
> might not work, given that I may need to do something different *after*
> the context manager, depending on if I was able to get a connection, but
> it's a thought. Options?





-- 
Steve
“Cheer up,” they said, “things could be worse.” So I cheered up, and sure
enough, things got worse.



More information about the Python-list mailing list