Stop writing Python 3.5 incompatible code :-)

Frank Millman frank at chagford.com
Thu Jan 14 02:36:59 EST 2016


"Chris Angelico"  wrote in message 
news:CAPTjJmpS+vfu33tULaE5oiVRvN_OTfuxrp8YLuy68qmu36-Nrw at mail.gmail.com...

> On Thu, Jan 14, 2016 at 5:27 PM, Frank Millman <frank at chagford.com> wrote:
> > Using LBYL, one would retrieve the row(s) and check the length. I found 
> > a
> > way to use EAFP, as follows -
> >
> > cur.execute('SELECT ...')
> > (row,) = cur
> >
> > This uses tuple unpacking, and only works if exactly one row is 
> > returned. If
> > it fails it raises ValueError, but I need to know whether it failed 
> > because
> > no rows were returned, or more than one row was returned. The only way I
> > could figure out how to achieve this was to parse the error message.
>
> That seems like a tricky way to do things. How about this:
>
> cur.execute(...)
> rows = iter(cur)
> try:
>     row = next(rows)
> except StopIteration:
>     # 0 rows returned
> try:
>     next(rows)
> except StopIteration: pass
> else:
>     # >1 rows returned
>
> It's a bit more verbose, but it's reliable. Alternatively, since you
> know that a returned row will never be None:
>
> cur.execute(...)
> rows = iter(cur)
> row, extra = next(rows), next(rows)
> if row is None:
>     # 0 rows returned
> if extra is not None:
>     # >1 rows returned
>

Thanks, Chris. I did feel unhappy relying on the contents of an error 
message, so I will follow up on your suggestion.

I like your first solution. In fact, with sqlite3, 'cur' is already 
iterable, so I can just say

cur.execute(...)
try:
    row = next(cur)
except StopIteration:
    # 0 rows returned
try:
    next(cur)
except StopIteration: pass
else:
    # >1 rows returned

I will have to check if this works with psycopg2 and pyodbc, the other two 
adaptors that I use.

Your second solution does not seem to work - if it raises StopIteration, 
neither 'row' nor 'extra' is populated. I may be doing it wrong, but no 
matter, I prefer the first one anyway.

There is one slight downside that I can see. I believe that raising an 
exception is a relatively expensive operation in Python. For this reason, a 
rule of thumb is to use EAFP when the usual case is to have no exception, 
and LBYL otherwise. With my approach, I do not get an exception if exactly 
one row is returned. With yours, you rely on raising an exception to 
determine the result.

I can think of two counter-arguments to that. Firstly, it is more important 
to ensure that the program is correct than to shave off a few microseconds. 
Secondly, as StopIteration is used extensively under the hood in modern 
pythons, it is probably highly optimised, and therefore nothing to be 
worried about.

Frank





More information about the Python-list mailing list