To genexp or not to genexp - a cautionary tale

I don’t know about you but I’ve been increasingly comfortable using a genexp style when passing sequences as parameters. It just saves a bit of syntax clutter and generally does exactly what you want. To give a completely trivial example:

a = range (10)
print sorted (i for i in a if i < 7)

But (there’s always a But) I found myself having unexpected problems with a database-row class I’d recently beefed up and I couldn’t work out why. It’s one of these things I’m sure everyone’s done where you pass it the cursor description and the row values and it does the __getattr__ and __getitem__ stuff for you. Well, it was acting as though the cursor wasn’t passing the column names at all. I suspected the new(ish) version of pyodbc I’d installed lately, but it was soon clear that the problem was in the generated Row class itself.

Of course, it turned out that I was, as above, passing the column names as a genexp. And some early reference was consuming the generator, silently leaving nothing for a later reference to consume. Something like this:

q.execute ("SELECT blah FROM blah")
Row = row.Row (d[0] for d in q.description)
return [Row (i) for i in q.fetchall ()]

where the code in row.Row did something like this:

def Row (names):
  something = "-".join (names)
  ## Oops, consumed the generator
  description = dict ((name, index) for index, name in enumerate (names))
  ## Oh dear, nothing left to consume but not an error

You see that I’m not falling into the trap of returning the list of rows as a genexp, because I know I’m likely to be using it in several places, but it’s just that bit cleaner to pass as a genexp as a parameter, and I fell for it.

One more thing to look out for.

Comments

Comments powered by Disqus