I was perusing the App Engine SDK and I came across this snippet:
if self.choices:
match = False
for choice in self.choices:
if choice == value:
match = True
if not match:
raise BadValueError('Property %s is %r; must be one of %r' %
(self.name, value, self.choices))
Since I don't work with many other Python programmers, I always have trouble
figuring out what interesting tidbits would be useful to post in, say, a blog
entry. I don't have a good understanding of the popular knowledge level, but I
figure that I can't go too wrong refactoring code written by Google engineers
(who I naively assume are all as cool as Steve Yegge). [*]
The for-else statement
Let's forget about self for now [†] and refactor to use an obscure (but
useful) Python feature, the for-else construct. for-else removes the
necessity for the boolean-flag-state idiom from the original code, which is
often used in lower level languages. [‡]
if choices:
for choice in choices:
if choice == value:
break
else:
raise BadValueError
The for-else statement looks a little strange when you first encounter it,
but I've come to love it. The else suite is evaluated if you don't
break out of the for loop. In this case, if we didn't break out of the
for loop, then we never found a value equivalent to choice.
We also gain some efficiency over the original by using the break statement
as soon as we find a match: there's no need to keep looking if you've already
found a result! This can save you from iterating over all len(choices)
items if you find it's a valid choice in the first iteration.
in (contains) operator
Here is an even more readable and Python-like refactoring that uses the
in operator: [§]
if choices and value not in choices:
raise BadValueError
The in operator works on any iterable object and performs the same behavior
as the code above: it looks for any item within self.choices such that
choice == item. If it finds it early in the list, it won't keep looking.
This is similar behavior to our early break statement from the first
refactoring.
Just like the original code with the for loop, the in operator raises a
TypeError if choices is not iterable. The in operator is
effectively a drop-in replacement for the (more verbose) for loop when it
comes to membership testing.
Footnotes
| [*] | You should read his blog if you don't already. |
| [†] | For the language lawyers: we're forgetting about the fact that this code
was intended to be executed in a bound instance method. ;) |
| [‡] | For example, C. For more information on programming languages and their
"heights", see this Wikipedia entry. |
| [§] | Yeah, yeah... technically it's the not in operator. |