10

I am trying to match a type in Python 3.10 using the console:

t = 12.0
match type(t):
  case int:
    print("int")
  case float:
    print("float")

And I get this error:

  File "<stdin>", line 2
SyntaxError: name capture 'int' makes remaining patterns unreachable

How can I fix this issue?

charelf
  • 3,103
  • 4
  • 29
  • 51
Pierre Thibault
  • 1,895
  • 2
  • 19
  • 22

3 Answers3

11

Lose the type() and also add parentheses to your types:

t = 12.0
match t:
  case int():
    print("int")
  case float():
    print("float")

I'm not sure why what you've wrote is not working, but this one works.

fish2000
  • 4,289
  • 2
  • 37
  • 76
mmohaveri
  • 528
  • 7
  • 23
  • It's odd that calling the `type` works instead of asking for the type in the `match` clause and `case`ing against the possibilities. It would be interesting to have some further information about why this works instead of the approach of most languages, as in the question. Nevertheless, it works. Thanks! – lsabi Jun 29 '22 at 20:50
  • 2
    @lsabi My understanding is that other languages mainly have `switch-case` structure, which is a fancy `if-else`. But what python's structural pattern matching is offering is far more powerful, it allows you to select a branch of code based on the structure of the object you're comparing. So here, you're not "saying if type of `t` is `int`", you're saying "if you can put `t` in an `int` container". But take my word with a grain of salt as that's just my understanding from different things that I've read regarding this new feature and I'm in no means an expert in this area. – mmohaveri Jun 30 '22 at 21:43
  • 1
    The PEP for Structural Pattern Matching is a great resource for seeing why this is the case. The type isn't "called" (though the syntax would make you think so), but rather it's set up for destructuring the object. See the PEP https://peps.python.org/pep-0622/#matching-process, where `case (x, y)` is used for tuple unpacking/destructuring, and you can imagine the same can be done with any type of data class. – Brendano257 Sep 13 '22 at 10:57
7

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! `tt = int`, 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.)

Dubslow
  • 553
  • 2
  • 6
  • 15
1

My fix was:

match type(t):
  case v if v is int:
    print("int")
  case v if v is float:
    print("float")

It is not very elegant but it is the best I can do with pattern matching.

charelf
  • 3,103
  • 4
  • 29
  • 51
Pierre Thibault
  • 1,895
  • 2
  • 19
  • 22