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