2

I was surprised to discover that "X in Y is not None" does not throw an exception. I can't figure out how Python actually interprets this. What value does "is not None" operate on? Does it serve any purpose here?

>>> "asdf" in {} is not None
False
>>> 
>>> "asdf" in {"asdf": 1} is not None
True
>>> 
J. Darnell
  • 519
  • 7
  • 15
  • 2
    That first example is pretty interesting, considering that `("asdf" in {}) is not None` returns `True`. – 0x5453 Feb 03 '20 at 19:42
  • `is` is a [comparison operator](https://docs.python.org/3/reference/expressions.html#comparisons); `not None` is the right-hand-side of the operation - it is being compared to the (evaluated) expression on the left-hand-side. `is`compares identity - `None` is an object as well as `True` and `False` - `None` will never **be** `True` or `False`. – wwii Feb 03 '20 at 19:42
  • 2
    @wwii good point. The left side should always return True/False, neither of which is None, so should the whole line always return False? – J. Darnell Feb 03 '20 at 19:44

2 Answers2

9

Python comparison chaining strikes again.

According to the same rules that says that 1 < 2 < 3 is equivalent to 1 < 2 and 2 < 3, these expressions:

"asdf" in {} is not None
"asdf" in {"asdf": 1} is not None

Are equivalent to:

"asdf" in {} and {} is not None
"asdf" in {"asdf": 1} and {"asdf": 1} is not None
that other guy
  • 116,971
  • 11
  • 170
  • 194
2

@that other guy already pointed out the issue, I figured I'd add a bit of explanation for those that don't see the point.

The key thing to realise here is that is not is similar to != and in is also in the same category - they are all comparison operators https://docs.python.org/2/reference/expressions.html#comparisons. And these operators work for all the types of the values surrounding them.

Because Python allows chaining comparison operators, the main thing is to figure out the order in which they will be evaluate and what they operate on.

Consider:

>>> "asdf" in {} is not None
False
>>> ("asdf" in {}) is not None
True
>>> ("asdf" in {}) and ({} is not None)
False
>>> {} is not None
True

So, the expression "asdf" in {} is not None breaks down into <value> <comparison> <value> <comparison> <value>, which Python evaluates as <value1> <comparison> <value2> and <value2> <comparison> <value3>, so ("asdf" in {}) and ({} is not None).

The reason Python doesn't see it as ("asdf" in {}) is not None, which makes sense coming from some other languages, is that Python doesn't want 4 > 3 > 2 to evaluate as (4 > 3) > 2 (which is False), but as (4 > 3) and (3 > 2) (which is True).

<x> is not None by the way means the identity of <x> is the same as the identity of None, in other words "<x> refers to the same thing as None", which is slightly different from saying they have the same value (or "they evaluate to the same"). Since there is only one None, if a variable name refers to None it is None.

Grismar
  • 27,561
  • 4
  • 31
  • 54