I have the following piece of code (expanded out for a clearer explanation) that I expected to always have boolean variable types. However, mypy indicates that the second and third types are not always bool
but instead a union of list
, None
, and bool
.
a: list[str] | None = None
b: list[str] | None = None
def f(test: str) -> bool:
flag: bool = not a and not b
flag2: bool = a and test in a
flag3: bool = b and test not in b
return flag or flag2 or flag3
If I change the second line to a is not None
(along with the third), the indicated typing becomes correct. Shouldn't the types resolve to boolean since the expressions are always conditionals? Why is the is not None
necessary?
Here is the playground link showing the error: https://mypy-play.net/?mypy=latest&python=3.11&flags=verbose&gist=14167cfb7554c2c94c3bd65d2710d017
and
in Python is not a Boolean operation, though it can be (and often is) used as one. a and b
will return the a
if it is falsy, or b
otherwise. To see why this definition can function as a Boolean operation:
False and False == False # (a is falsy, result is a)
False and True == False # (a is falsy, result is a)
True and False == False # (a is not falsy, result is b)
True and True == True # (a is not falsy, result is b)
However, with non-Boolean operands:
"" and 0 == "" # (a is falsy, result is a)
"" and 42 == "" # (a is falsy, result is a)
"answer" and 0 == 0 # (a is not falsy, result is b)
"answer" and 42 == 42 # (a is not falsy, result is b)
When coerced to a Boolean, e.g. by a conditional, it still functions as if the result was Boolean, but it is not. Thus, the type of the result of an and
operation is a union of the types of its arguments. Since a
is list[str] | None
and test in a
is a bool
, a and test in a
is list[str] | None | bool
. Specifically, if a
is []
, which still falls under list[str]
, then flag2
would end up as a non-Boolean []
.