This is a common "gotcha" of the new syntax: case clauses are not expressions. That is, if you put a variable name in a case clause, the syntax assigns to that name rather than reading that name.
It's a common misconception to think of match
as like switch
in other languages: it is not, not even really close. switch
cases are expressions which test for equality against the switch
expression; conversely, match
cases are structured patterns which unpack the match
expression. It's really much more akin to generalized iterable unpacking. It asks the question: "does the structure of the match
expression look like the structure of the cause clause?", a very different question from what a switch
statement asks.
For example:
t = 12.0
match t:
case newvar: # This is equal to `newvar = t`
print(f"bound a new variable called newvar: {newvar}")
# prints "bound a new variable called newvar: 12.00000000"
# this pattern matches anything at all, so all following cases never run
case 13.0:
print("found 13.0")
case [a, b, c]: # matches an iterable with exactly 3 elements,
# and *assigns* those elements to the variables `a`, `b` and `c`
print(f"found an iterable of length exactly 3.")
print(f"these are the values in the iterable: {a} {b} {c}")
case [*_]:
print("found some sort of iterable, but it's definitely")
print("not of length 3, because that already matched earlier")
case my_fancy_type(): # match statement magic: this is how to type check!
print(f"variable t = {t} is of type {my_fancy_type}")
case _:
print("no match")
So what your OP actually does is kinda like this:
t = 12.0
tt = type(t) # float obviously
match tt:
case int: # assigns to int! `int = tt`, overwriting the builtin
print(f"the value of int: {int}")
# output: "the value of int: <class 'float'>"
print(int == float) # output: True (!!!!!!!!)
# In order to get the original builtin type, you'd have to do
# something like `from builtins import int as int2`
case float: # assigns to float, in this case the no-op `float = float`
# in fact this clause is identical to the previous clause:
# match anything and bind the match to its new name
print(f"match anything and bind it to name 'float': {float}")
# never prints, because we already matched the first case
case float(): # since this isn't a variable name, no assignment happens.
# under the hood, this equates to an `isinstance` check.
# `float` is not an instance of itself, so this wouldn't match.
print(f"tt: {tt} is an instance of float") # never prints
# of course, this case never executes anyways because the
# first case matches anything, skipping all following cases
Frankly, I'm not entirely sure how the under-the-hood instance check works, but it definitely works like the other answer says: by defintion of the match
syntax, type checks are done like this:
match instance:
case type():
print(f"object {instance} is of type {type}!")
So we come back to where we started: case clauses are not expressions. As the PEP says, it's better to think of case clauses as kind of like function declarations, where we name the arguments to the function and possibly bind some default values to those newly-named arguments. But we never, ever read existing variables in case clauses, only make new variables. (There's some other subtleties involved as well, for instance a dotted access doesn't count as a "variable" for this purpose, but this is complicated already, best to end this answer here.)