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 case 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.)

Answer from Dubslow on Stack Overflow
🌐
Python
peps.python.org › pep-0636
PEP 636 – Structural Pattern Matching: Tutorial | peps.python.org
The __match_args__ special attribute defines an explicit order for your attributes that can be used in patterns like case Click((x,y)). Your pattern above treats all mouse buttons the same, and you have decided that you want to accept left-clicks, and ignore other buttons. While doing so, you notice that the button attribute is typed as a Button which is an enumeration built with enum.Enum.
🌐
Plain English Westminster
benhoyt.com › writings › python-pattern-matching
Structural pattern matching in Python 3.10
Originally I was also concerned that match’s class patterns don’t play well with Python’s use of duck typing, where you just access attributes and call methods on an object, without checking its type first (for example, when using file-like objects). With class patterns, however, you specify the type, and it performs an isinstance check.
Top answer
1 of 4
22

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 case 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.)

2 of 4
17

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.

🌐
Python
peps.python.org › pep-0622
PEP 622 – Structural Pattern Matching | peps.python.org
A match statement compares a value (the subject) to several different shapes (the patterns) until a shape fits. Each pattern describes the type and structure of the accepted values as well as the variables where to capture its contents.
🌐
Better Stack
betterstack.com › community › guides › scaling-python › python-pattern-matching
Structural Pattern Matching in Python: A Comprehensive Guide | Better Stack Community
Learn how to use structural pattern matching in Python to simplify complex conditionals and make your code more readable. This guide covers matching with basic types, dictionaries, guard clauses, and more—introduced in Python 3.10 and enhanced in Python 3.13.
🌐
Real Python
realpython.com › structural-pattern-matching
Structural Pattern Matching in Python – Real Python
August 6, 2024 - Python compares the subject against each of your patterns to determine which branch of code to execute. The subject must match a given pattern exactly to make it succeed. Strings aren’t the only literals you can use in this kind of structural pattern. Python supports a few more data types, including numbers and Boolean values.
Top answer
1 of 4
22

If you just pass a type directly, it will consider it to be a "name capture" rather than a "value capture." You can coerce it to use a value capture by importing the builtins module, and using a dotted notation to check for the type.

import builtins
from typing import Type


def main(type_: Type):
    match (type_):
        case builtins.str:  # it works with the dotted notation
            print(f"{type_} is a String")
        case builtins.int:
            print(f"{type_} is an Int")
        case _:
            print("\nhttps://en.meming.world/images/en/0/03/I%27ve_Never_Met_This_Man_In_My_Life.jpg")

if __name__ == "__main__":
    main(type("hello"))  # <class 'str'> is a String
    main(str)  # <class 'str'> is a String
    main(type(42))  # <class 'int'> is an Int
    main(int)  # <class 'int'> is an Int
2 of 4
7

As its name suggests, structural pattern matching is more suited for matching patterns, not values (like a classic switch/case in other languages). For example, it makes it very easy to check different possible structures of a list or a dict, but for values there is not much advantage over a simple if/else structure:

if type_to_match == str:
    print("This is a String")
elif type_to_match == int:
    print("This is an Int")
else:
    print("\nhttps://en.meming.world/images/en/0/03/I%27ve_Never_Met_This_Man_In_My_Life.jpg")

But if you really want to use SPM, you could use the guard feature along with issublcass to check if a type is or is a child of any other:

match type_to_match:
    case s if issubclass(type_to_match, str):
        print(f"{s} - This is a String")
    case n if issubclass(type_to_match, int):
        print(f"{n} - This is an Int")
    case _:
        print("\nhttps://en.meming.world/images/en/0/03/I%27ve_Never_Met_This_Man_In_My_Life.jpg")
🌐
Earthly
earthly.dev › blog › structural-pattern-matching-python
Structural Pattern Matching in Python - Earthly Blog
July 19, 2023 - They include a subject with one of the basic data types (integer, float, string, and Boolean) matched against a pattern of the same data type. The behavior of the match-case statement, in this case, is similar to the switch-case statement in ...
Find elsewhere
🌐
ArjanCodes
arjancodes.com › blog › how-to-use-structural-pattern-matching-in-python
Introduction to Structural Pattern Matching in Python | ArjanCodes
June 20, 2024 - Structural pattern matching is a feature that allows you to check a value against a pattern, extracting parts of the value if the pattern matches. This can make complex conditionals simpler and more readable.
🌐
Medium
medium.com › data-engineering-with-dremio › leveraging-pythons-pattern-matching-and-comprehensions-for-data-analytics-9293a7921db5
Leveraging Python’s Pattern Matching and Comprehensions for Data Analytics | by Alex Merced | Data, Analytics & AI with Dremio | Medium
November 7, 2024 - Unlike traditional if-else chains, pattern matching lets you define specific patterns that Python will match against, simplifying code that deals with various data formats and nested structures. With pattern matching, data analysts can write expressive code to handle different data transformations and formats with minimal boilerplate. For instance, when working with datasets that contain multiple types of values — like dictionaries, nested lists, or JSON objects — pattern matching can help categorize, transform, or validate data based on structure and content.
Top answer
1 of 1
7

To expand on what I said in comments: match introduces a value, but case introduces a pattern to match against. It is not an expression that is evaluated. In case the pattern represents a class, the stuff in the parentheses is not passed to a constructor, but is matched against attributes of the match value. Here is an illustrative example:

class Tree:
    def __init__(self, name):
        self.kind = name
    
def what_is(t):
    match t:
        case Tree(kind="oak"):
            return "oak"
        case Tree():
            return "tree"
        case _:
            return "shrug"

print(what_is(Tree(name="oak")))    # oak
print(what_is(Tree(name="birch")))  # tree
print(what_is(17))                  # shrug

Note here that outside case, Tree(kind="oak") would be an error:

TypeError: Tree.__init__() got an unexpected keyword argument 'kind'

And, conversely, case Tree(name="oak") would never match, since Tree instances in my example would not normally have an attribute named name.

This proves that case does not invoke the constructor, even if it looks like an instantiation.


EDIT: About your second error: you wrote case type(Tree()):, and got TypeError: type() accepts 0 positional sub-patterns (1 given). What happened here is this: case type(...) is, again, specifying a pattern. It is not evaluated as an expression. It says the match value needs to be of type type (i.e. be a class), and it has to have attributes in the parentheses. For example,

match Tree:
    case type(__init__=initializer):
        print("Tree is a type, and this is its initializer:")
        print(initializer)

This would match, and print something like

# => Tree is a type, and this is its initializer:
#    <function Tree.__init__ at 0x1098593a0>

The same case would not match an object of type Tree, only the type itself!

However, you can only use keyword arguments in this example. Positional arguments are only available if you define __match_args__, like the documentation says. The type type does not define __match_args__, and since it is an immutable built-in type, case type(subpattern, ...) (subpattern! not subexpression!), unlike case type(attribute=subpattern, ...), will always produce an error.

🌐
Coditation
coditation.com › blog › structural-pattern-matching-in-python-i
Structural Pattern Matching in Python I
Structural pattern matching can make your code more expressive and easier to read, as it allows you to directly compare the structure of objects without having to write long and complex type-checking code. It is available in languages such as Scala, Rust, Java (as of version 14), Python (as of version 3.10) In python, a pattern is a value defined after the case keyword which acts as a minimal structural constraint against which a subject is matched...
🌐
Buddy
buddy.works › home › tutorials › languages › structural pattern matching in python
Structural Pattern Matching In Python | Tutorials
April 28, 2021 - If any of them is matched, the match succeeds and we return "Number". In an OR pattern, all sub patterns must bind to the same variables. In our example above, the sub patterns bind to val. AS patterns allow us to specify a structure constraint and bind to a value at the same time. In the check_type function that we used to demonstrate OR patterns, notice we repeated the variable val. We can avoid this by rewriting our pattern as an AS pattern: pythondef check_type(val): match val: case str() as val: return "String" case int() | float() as val: return "Number"
🌐
Inspired Python
inspiredpython.com › course › pattern-matching › mastering-structural-pattern-matching
Mastering Structural Pattern Matching • Inspired Python
To use the structural pattern matching engine you must define patterns that are meaningful to you and Python and trust that Python will work out how to get you the answer. Now you know that a pattern is a way of articulating a desired structure ...
🌐
Martin Heinz
martinheinz.dev › blog › 78
Recipes and Tricks for Effective Structural Pattern Matching in Python | Martin Heinz | Personal Website & Blog
August 2, 2022 - Common use case for match/case is efficient matching of JSON structures in form of Python's dictionaries. This can be done with mapping pattern which is triggered by case {...}: ...
🌐
InfoWorld
infoworld.com › home › software development › programming languages › python
How to use structural pattern matching in Python | InfoWorld
August 9, 2023 - In fact, the chief use case for structural pattern matching is to match patterns of types, rather than patterns of values. Python performs matches by going through the list of cases from top to bottom. On the first match, Python executes the statements in the corresponding case block, then skips to the end of the match block and continues with the rest of the program.
🌐
Turingtaco
turingtaco.com › pattern-matching-lists-and-dictionaries-in-python
Pattern Matching Lists and Dictionaries in Python
December 7, 2024 - Pattern matching with tuples in Python operates under the same principles as lists, but we'll use parenthesis instead of square brackets. Besides that, the process has a crucial distinction of the underlying type.
🌐
Reddit
reddit.com › r/learnpython › how to use structural pattern matching to match built-in types?
r/learnpython on Reddit: How to use Structural Pattern Matching to match built-in types?
January 26, 2022 -

Hello,

I'm trying to use SPM to test if some variable is an int or a str and I can't seem to make it work.

The following code:

from typing import Type

def main(type_to_match: Type):
    match type_to_match:
        case str():
            print("This is a String")
        case int():
            print("This is an Int")
        case _:
            print("\nhttps://en.meming.world/images/en/0/03/I%27ve_Never_Met_This_Man_In_My_Life.jpg")

if __name__ == "__main__":
    test_type = str
    main(test_type)

Outputs: https://en.meming.world/images/en/0/03/I%27ve_Never_Met_This_Man_In_My_Life.jpg

I fail to see what I'm doing wrong.

EDIT: A very helpful SO user found a solution. Here it is, if you're interseted:

import builtins
from typing import Type


def main(type_: Type):
    match (type_):
        case builtins.str:  # it works with the dotted notation
            print(f"{type_} is a String")
        case builtins.int:
            print(f"{type_} is an Int")
        case _:
            print("\nhttps://en.meming.world/images/en/0/03/I%27ve_Never_Met_This_Man_In_My_Life.jpg")

if __name__ == "__main__":
    main(type("hello"))  # <class 'str'> is a String
    main(str)  # <class 'str'> is a String
    main(type(42))  # <class 'int'> is an Int
    main(int)  # <class 'int'> is an Int
🌐
Python
peps.python.org › pep-0634
PEP 634 – Structural Pattern Matching: Specification | peps.python.org
For example, collections.defaultdict instances will only be matched by patterns with keys that were already present when the match statement was entered. ... class_pattern: | name_or_attr '(' [pattern_arguments ','?] ')' pattern_arguments: | positional_patterns [',' keyword_patterns] | keyword_patterns positional_patterns: ','.pattern+ keyword_patterns: ','.keyword_pattern+ keyword_pattern: NAME '=' pattern · A class pattern may not repeat the same keyword multiple times. If name_or_attr is not an instance of the builtin type, TypeError is raised.
🌐
Hillel Wayne
hillelwayne.com › post › python-abc
Crimes with Python's Pattern Matching
July 31, 2022 - The pattern matching is flexible but also fairly limited. It can only match on an object’s type, meaning we have to make a separate ABC for each thing we want to test. Fortunately, there’s a way around this. Python is dynamically typed.