OR or |
A case of funny Python foibles
I’ve written previously about some funny Python behaviors and want to share another one I just encountered.
As part of a debugging interview I deliver when interviewing candidates at Miravoice (plug: we’re hiring!), I have a line of code that basically just evaluates how much the candidate is paying attention and thinking about basic logical implications:
class AClass:
def __init__(
self,
x: set[str] | None = {a set with some values},
y: set[str] | None = {another set with other values},
) -> None:
def is_x(self, value: str) -> bool:
return value in (self.x | self.y)A beginner Python programmer can glance at this code and grok what’s happening, at least at a high level. is_x is basically a less verbose but more computationally expensive version of: return True if value is in self.x OR if value is in self.y . Note, the original is more computationally complex because | creates a new set unioning self.x and self.y, while the more verbose version simply looks for the value in one set and then the other.
The point of the bug is for the candidate to recognize “Wait, the function is called is_x , and we’re using it when we want to check for only x, but for some reason, the function is making a union of both sets before checking. Most candidates see the results of this bug when they run the program or deep-read the code, rewrite it to return value in self.x, and move on.
Today, however, a candidate, to make the code more readable for himself, rewrote the function to return value in (self.x or self.y). I didn’t think anything of this rewrite when he did it, other than wondering why he didn’t just remove self.y and be done with it. I figured it would have no effect on the program output because I read the code as English instead of Python.
I was shocked to discover that return value in (self.x or self.y) produced the same output as return value in self.x ! The correct output! It took me aback, and I had to test the code a couple of times for myself to confirm that this was the case. Before I explain what I discovered, see if you can think through for yourself why return value in (self.x or self.y) evaluates to the same output as return value in self.x in our situation.
It turns out, | is not just pretty syntax for OR! Like I explained earlier, | is actually a set operation, finding the union of the two sets on either side of the operator. OR, however, is in this case a logical operation that first checks the set on the left and, if that set isn’t empty, None , or False (if it’s “truthy”, to use Python lingo), returns that left set. If, however, the left set isn’t “truthy”, OR returns the right set.
In our case, since x is truthy, self.x or self.y returns self.x, so return value in (self.x or self.y) logically evaluates to return value in self.x .
That’s pretty nuts! And a good reminder: never take Python at face value.
