如何使用 class 类型的匹配大小写

How to use match case with a class type

我想使用 match 根据 class type 确定要执行的操作。我似乎无法弄清楚该怎么做。我知道他们是实现这一目标的其他方式,我只是想知道是否可以通过这种方式完成。我不是在寻找有很多解决方法。


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)

输出结果如下:

aaa
bbb
???
???

尝试在 match 行中使用 typ() 而不是 typ

        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)        

输出:

aaa
bbb
aaa
bbb

更新:

根据 OP 的评论,要求提供比问题中的示例 classes 更适用于 classes 的解决方案,这里是解决此问题的答案:

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)

输出:

aaa
bbb
aaa
bbb

更新#2: 基于 post and some perusal of PEP 364 here,我创建了一个示例来展示一些数据类型(一个 Python 内置,一个来自集合模块的 class,以及一个用户定义的 class)被 match 用于根据 class 类型(或更一般地,数据类型)确定要执行的操作:

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)

输出:

aaa
str
Counter

另一个 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.

换句话说,如果我们尝试说 case aaa:aaa 将被解释为我们分配主题的名称(您的代码中的 typ)和将始终匹配并阻止任何匹配后续 case 行的尝试。

为了解决这个问题,对于可以使用点指定的 class 类型名称(或一般名称)(可能是因为它们属于名称空间或另一个 class),我们可以使用带点的名称作为不会被解释为名称捕获的模式。

对于built-in类型str,我们可以使用case __builtins__.str:。对于Python的collections模块中的Counterclass,我们可以使用case collections.Counter:。如果我们在另一个名为 namespacing_class 的 class 中定义 class aaa,我们可以使用 case namespacing_class.aaa:.

但是,如果我们在 Python 代码的顶层定义 class bbb,我不清楚是否有任何方法可以使用带点的名称来引用到它,从而避免名称捕获。

可能有一种方法可以在 case 行中指定用户定义的 class type,我只是还没有弄明白。否则,能够对可点类型而不是 non-dottable 类型执行此操作似乎相当武断(并且不幸)。

您想匹配一个常量值。 constant value patterns:

就是这种情况
match typ:
    case somemodule.ClassOne:
        ...
    case anothermodule.ClassTwo:
        ...

常量值模式必须采用 NAME ('.' NAME)+ 形式 - 即名称后跟至少一个属性查找。裸名将被视为捕获模式。这对于在其他模块中定义的 类 没问题,但是如果你想匹配同一个模块中的 类,你可以导入当前模块:

# in somemodule.py
import somemodule

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

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

或者如果你想避免循环导入,你可以创建一个命名空间:

import types

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

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

请注意,如果您采用“导入当前模块”路线,则需要注意 Python 的奇怪之处,其中入口点脚本被视为 __main__ 模块,无论它的文件名。如果您执行 python somefile.py 并尝试在其中执行 import somefile,它将执行 somefile.pysecond 运行 作为 somefile 模块并创建所有 类 的第二个副本,你的匹配语句将不起作用。