"Chris Angelico" wrote in message news:captjjmps+vfu33tulae5oivrvn_otfuxrp8yluy68qmu36-...@mail.gmail.com...

On Thu, Jan 14, 2016 at 5:27 PM, Frank Millman <fr...@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


--
https://mail.python.org/mailman/listinfo/python-list

Reply via email to