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.

Answer from Amadan on Stack Overflow
🌐
Python
peps.python.org › pep-0636
PEP 636 – Structural Pattern Matching: Tutorial | peps.python.org
The fourth pattern captures two values, which makes it conceptually similar to the unpacking assignment (x, y) = point. If you are using classes to structure your data you can use the class name followed by an argument list resembling a constructor, but with the ability to capture attributes into variables: from dataclasses import dataclass @dataclass class Point: x: int y: int def where_is(point): match point: case Point(x=0, y=0): print("Origin") case Point(x=0, y=y): print(f"Y={y}") case Point(x=x, y=0): print(f"X={x}") case Point(): print("Somewhere else") case _: print("Not a point")
🌐
Plain English Westminster
benhoyt.com › writings › python-pattern-matching
Structural pattern matching in Python 3.10
Python evaluates the match expression, and then tries each case from the top, executing the first one that matches, or the _ default case if no others match. But here’s where the structural part comes in: the case patterns don’t just have to be literals. The patterns can also: ... Wow! That’s a lot of features. Let’s see if we can use them all in one go, to see what they look like in a very contrived example (for a more gradual introduction, read the tutorial): class Car: __match_args__ = ('key', 'name') def __init__(self, key, name): self.key = key self.name = name expr = eval(input('
Discussions

You can use 3.10's new structural pattern matching feature to easily flatten deeply nested lists, tuples and sets.
I programmatically solved this in python 3.8, I'm excited to see how 3.10 simplifies this. Thanks for the post More on reddit.com
🌐 r/Python
60
534
November 22, 2021
python - How to use match case with a class type - Stack Overflow
In other words, if we try to say ... to match subsequent case lines. To get around this, for class type names (or names generally) that can be specified using a dot (perhaps because they belong to a namespace or another class), we can use the dotted name as a pattern that will not be interpreted as a name capture. For built-in type str, we can use case __builtins__.str:. For the Counter class in Python's collections ... More on stackoverflow.com
🌐 stackoverflow.com
How to do structural pattern matching in Python 3.10 with a type to match? - Stack Overflow
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 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... More on stackoverflow.com
🌐 stackoverflow.com
Adding pattern matching to Python

A very rough sketch. You have to put it on the python-ideas mailing list first. 99.99% of suggestions get shot down in flames by highly skilled Python developers, not all of whom are core developers. The 0.01% that survive end up with a PEP, but someone who wants the idea has to do the work writing the thing in the first place. Then it gets torn to pieces on the python-dev mailing list. If and only if it survives that process then the PEP gets approved by the BDFL or somebody that he has delegated to do the job. Then all that someone has to do is write the code, docs and tests, get them all peer reviewed and accepted, and lo and behold, there is your idea in the next x.y.0 version of Python. Simples :-)

More on reddit.com
🌐 r/Python
14
35
February 27, 2014
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.

🌐
Real Python
realpython.com › structural-pattern-matching
Structural Pattern Matching in Python – Real Python
August 6, 2024 - You still use capture patterns, but you augment them with conditional statements to ensure that the patterns only match when their respective guard condition is true. This works fine but leads to overly verbose code. Fortunately, there’s a better way involving a value pattern, which lets you recognize variables that should be treated as constants. These typically belong to some namespace, such as a class, enum, or a Python module, which you can access with the dot operator:
🌐
Python
peps.python.org › pep-0622
PEP 622 – Structural Pattern Matching | peps.python.org
A mapping pattern looks like {"user": u, "emails": [*es]}. It matches mappings with at least the set of provided keys, and if all the sub-patterns match their corresponding values.
🌐
Python documentation
docs.python.org › 3 › library › re.html
re — Regular expression operations
4 days ago - An enum.IntFlag class containing the regex options listed below. ... Make \w, \W, \b, \B, \d, \D, \s and \S perform ASCII-only matching instead of full Unicode matching. This is only meaningful for Unicode (str) patterns, and is ignored for bytes patterns.
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 - The __match_args__ attribute is a tuple that specifies the attribute names for pattern matching. By default, it’s an empty tuple, meaning no attributes can be extracted. class Point: __match_args__ = ("x", "y") def __init__(self, x, y): self.x = x self.y = y
🌐
Hillel Wayne
hillelwayne.com › post › python-abc
Crimes with Python's Pattern Matching
July 31, 2022 - Python is dynamically typed. 99% of the time this just means “you don’t need static types if you’re okay with things crashing at runtime”. But it also means that type information exists at runtime, and that types can be created at runtime. Can we use this for pattern matching? Let’s try it: def Not(cls): class _Not(ABC): @classmethod def __subclasshook__(_, C): return not issubclass(C, cls) return _Not def f(x): match x: case Not(DistanceMetric)(): print(f"{x} is not a point") case _: print(f"{x} is a point")
🌐
Earthly
earthly.dev › blog › structural-pattern-matching-python
Structural Pattern Matching in Python - Earthly Blog
July 19, 2023 - If positional patterns are present in a class, they are converted to keyword patterns based on the arrangement in the __match_args__ attribute. In this tutorial, we delved into Python 3.10’s structural pattern matching feature.
🌐
Martin Heinz
martinheinz.dev › blog › 78
Recipes and Tricks for Effective Structural Pattern Matching in Python | Martin Heinz | Personal Website & Blog
August 2, 2022 - Hopefully, future versions of Python will include some level of code analysis that might catch at least some of these issues. Another hard to debug issue you might encounter stems from missing a valid branch/case - that is - when match doesn't cover all the possible cases. This can be mitigated by adding safety asserts like so: from enum import Enum from typing import NoReturn class Color(Enum): RED = "Red" GREEN = "Green" BLUE = "Blue" def exhaustiveness_check(value: NoReturn) -> NoReturn: assert False, 'This code should never be reached, got: {0}'.format(value) def some_func(color: Color) -> str: match color: case Color.RED: return "Color is red." case Color.GREEN: return "Color is green." exhaustiveness_check(color) some_func(Color.RED)
🌐
Buddy
buddy.works › home › tutorials › languages › structural pattern matching in python
Structural Pattern Matching In Python | Tutorials
April 28, 2021 - Class patterns fulfill two purposes: checking whether a given subject is indeed an instance of a specific class, and extracting data from specific attributes of the subject. Consider the function below that sums two integers.
🌐
Inspired Python
inspiredpython.com › course › pattern-matching › python-pattern-matching-examples-etl-and-dataclasses
Python Pattern Matching Examples: ETL and Dataclasses • Inspired Python
Python’s dataclasses module comes with a handy asdict helper function that pulls the typed attributes out of the object into a dictionary again so the CSV writer module knows how to store the data. And that’s it. Pattern Matching is a natural way of expressing the structure of data and extracting the information you want
Top answer
1 of 4
15

You want to match against a constant value. That's a case for constant value patterns:

match typ:
    case somemodule.ClassOne:
        ...
    case anothermodule.ClassTwo:
        ...

Constant value patterns must be of the form NAME ('.' NAME)+ - that is, a name followed by at least one attribute lookup. A bare name will be considered a capture pattern. That's fine for classes defined in other modules, but if you want to match against classes in the same module, you can import the current module:

# in somemodule.py
import somemodule

class ClassOne:
    ...
class ClassTwo:
    ...

match typ:
    case somemodule.ClassOne:
        ...
    case somemodule.ClassTwo:
...

or if you want to avoid the circular import, you can create a namespace:

import types

options = types.SimpleNamespace()
options.ClassOne = ClassOne
options.ClassTwo = ClassTwo

match typ:
    case options.ClassOne:
        ...
    case options.ClassTwo:
        ...

Note that if you go the "import the current module" route, you need to be aware of Python's weird thing where the entry point script is considered the __main__ module, regardless of its file name. If you do python somefile.py and try to import somefile inside that, it will perform a second run of somefile.py as the somefile module and create a second copy of all your classes, and your match statements won't work.

2 of 4
6

Try using typ() instead of typ in the match line:

        class aaa():
            pass

        class bbb():
            pass

        def f1(typ):
            if typ is aaa:
                print("aaa")
            elif typ is bbb:
                print("bbb")
            else:
                print("???")

        def f2(typ):
            match typ():
                case aaa():
                    print("aaa")
                case bbb():
                    print("bbb")
                case _:
                    print("???")

        f1(aaa)
        f1(bbb)
        f2(aaa)
        f2(bbb)        

Output:

aaa
bbb
aaa
bbb

UPDATE:

Based on OP's comment asking for solution that works for classes more generally than the example classes in the question, here is an answer addressing this:

class aaa():
    pass

class bbb():
    pass

def f1(typ):
    if typ is aaa:
        print("aaa")
    elif typ is bbb:
        print("bbb")
    else:
        print("???")

def f2(typ):
    match typ.__qualname__:
        case aaa.__qualname__:
            print("aaa")
        case bbb.__qualname__:
            print("bbb")
        case _:
            print("???")

f1(aaa)
f1(bbb)
f2(aaa)
f2(bbb)

Output:

aaa
bbb
aaa
bbb

UPDATE #2: Based on this post and some perusal of PEP 634 here, I have created an example showing how a few data types (a Python builtin, a class from the collections module, and a user defined class) can be used by match to determine an action to perform based on a class type (or more generally, a data type):

class bbb:
    pass

class namespacing_class:
    class aaa:
        pass


def f1(typ):
    if typ is aaa:
        print("aaa")
    elif typ is bbb:
        print("bbb")
    else:
        print("???")

def f2(typ):
    match typ.__qualname__:
        case aaa.__qualname__:
            print("aaa")
        case bbb.__qualname__:
            print("bbb")
        case _:
            print("???")

def f3(typ):
    import collections
    match typ:
        case namespacing_class.aaa:
            print("aaa")
        case __builtins__.str:
            print("str")
        case collections.Counter:
            print("Counter")
        case _:
            print("???")

'''
f1(aaa)
f1(bbb)
f2(aaa)
f2(bbb)
'''
f3(namespacing_class.aaa)
f3(str)
import collections
f3(collections.Counter)

Outputs:

aaa
str
Counter

As stated in this answer in another post:

A variable name in a case clause is treated as a name capture pattern. It always matches and tries to make an assignment to the variable name. ... We need to replace the name capture pattern with a non-capturing pattern such as a value pattern that uses the . operator for attribute lookup. The dot is the key to matching this a non-capturing pattern.

In other words, if we try to say case aaa: for example, aaa will be interpreted as a name to which we assign the subject (typ in your code) and will always match and block any attempts to match subsequent case lines.

To get around this, for class type names (or names generally) that can be specified using a dot (perhaps because they belong to a namespace or another class), we can use the dotted name as a pattern that will not be interpreted as a name capture.

For built-in type str, we can use case __builtins__.str:. For the Counter class in Python's collections module, we can use case collections.Counter:. If we define class aaa within another class named namespacing_class, we can use case namespacing_class.aaa:.

However, if we define class bbb at the top level within our Python code, it's not clear to me that there is any way to use a dotted name to refer to it and thereby avoid name capture.

It's possible there's a way to specify a user defined class type in a case line and I simply haven't figured it out yet. Otherwise, it seems rather arbitrary (and unfortunate) to be able to do this for dottable types and not for non-dottable ones.

🌐
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.
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.

🌐
Coditation
coditation.com › blog › structural-pattern-matching-in-python-iii
Structural Pattern Matching in Python III
# Matching a class pattern with argument where python class have a `__match_args__` class attribute class Furniture: __match_args__ = ("price", "name") def __init__(self, name: str, price: float): self.name = name self.price = price subject = Furniture("table", 2200) # Output: The cost of this table is 2200 match subject: case Furniture("table", 2200): print(f"This {subject.name} will cost around {subject.price}") case Furniture(2200, "table"): print(f"The cost of this {subject.name} is {subject.price}")
🌐
Python Course
python-course.eu › python-tutorial › structural-pattern-matching.php
17. Structural Pattern Matching | Python Tutorial | python-course.eu
March 29, 2023 - With structural pattern matching, a data structure (such as a list or a tuple) is analyzed and searched for a specific pattern that one has previously defined. One can then perform different actions based on the patterns found.
🌐
GeeksforGeeks
geeksforgeeks.org › pattern-matching-python-regex
Pattern matching in Python with Regex - GeeksforGeeks
July 6, 2024 - (Ha){3, } will match three or more instances of the (Ha) group, while (Ha){, 5} will match zero to five instances. Curly brackets can help make your regular expressions shorter. Example 1: In this example, we will use curly brackets to specify ...