I want to use match
to determine an action to perform based on a class type
. I cannot seem to figure out how to do it. I know their are other ways of achieving this, I would just like to know can it be done this way. I am not looking for workarounds of which there are many.
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)
The output is as follows:
aaa
bbb
???
???
CodePudding user response:
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 364 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.
CodePudding user response:
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.