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.
You can use 3.10's new structural pattern matching feature to easily flatten deeply nested lists, tuples and sets.
python - How to use match case with a class type - Stack Overflow
How to do structural pattern matching in Python 3.10 with a type to match? - Stack Overflow
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.comVideos
It's a pretty nifty feature and it's a much easier to extend or extend, like selectively flattening some values in a dictionary based on the key, for instance. I've written about it extensively on Mastering Structural Pattern Matching
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.
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.
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.)
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.