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.
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.
Videos
What are your thoughts on it.
Rather than match type(v), match v directly:
values = [
1,
"hello",
True,
]
for v in values:
match v:
case str():
print("It is a string!")
case bool():
print("It is a boolean!")
case int():
print("It is an integer!")
case _:
print(f"It is a {type(v)}!")
Note that I've swapped the order of bool() and int() here, so that True being an instance of int doesn't cause issues.
This is a class pattern match.
You can match directly against the type of v, but you need a value pattern to refer to the types to match, as a "dotless" name is a capture pattern that matches any value. For example,
import builtins
values = [
1,
"hello",
True
]
# Caveat: this will continue to work even if someone
# rebinds the built-in, but not, for example, if builtins.str
# itself is rebound.
for v in values:
match type(v):
case builtins.str:
print("It is a string!")
case builtins.int:
print("It is an integer!")
case builtins.bool:
print("It is a boolean!")
case _:
print(f"It is a {type(v)}!")
Note that a value pattern must be a dotted name; it's not an arbitrary expression that can evaluate to a specific value.
(Whether you really want to match against the actual type of a value, or really want to determine if a value is an instance of a given type, is another matter. In the latter case, an if-elif statement is needed.
if isinstance(v, bool):
print("It is a boolean!")
elif isinstance(v, int):
print("It is an int!")
elif isinstance(v, str):
print("It is a string!")
else:
print(f"It is a {type(v)}!")
There is no pattern that lets you use the result of calling isinstance as the case to match against.
)