Class 模式匹配错误的大小写

Class pattern is matching the wrong cases

我正在编写一个对象序列化程序,但遇到 class patterns 与预期情况不匹配的问题:

def dump_obj(x):
    match(x):
        case list():
            emit('L')
            dump_obj(len(x))
            for elem in x:
                dump_obj(elem)
        case Iterable():
            emit('I')
            dump_obj((type(x), list(x)))
        case tuple():
            emit('T')
            dump_obj(list(x))
        case str():
            emit('S')
            dump_obj(len(x))
            emit(x)
        case int():
            emit('D')
            emit(str(x))
        case _:
            raise TypeError(f'Unknown obj {x!r}')

当我用一个元组调用 dump_obj() 时,它给出了 I-case 的无限递归迭代而不是匹配 T-case 的元组.

当我用列表子类调用 dump_obj() 时,它匹配列表的 L-case,而不是 iterables 的预期 I-case。

第一个问题:排序

这些案例并不是相互独立的。它们是从 top-down 开始测试的(就像一个长 if/elif 链),第一个匹配的获胜。

在示例中,特定匹配测试如 listtuplestr 需要在 Iterable 等更一般的匹配之前出现。否则使用当前代码,像 (10, 20, 30) 这样的元组输入将匹配 I-case 而不是预期的 T-case.

第二个问题:特异性

一个class模式执行isinstance() check which would match both a type and subclasses of the type. To restrict the case to an exact match, use a type guard:

case list() if type(x) == list:
    ...

综合起来

应用了两种解决方案后,这里是新代码:

def dump_obj(x):
    match(x):
        case list() if type(x) == list:   # <-- Added guard
            emit('L')
            dump_obj(len(x))
            for elem in x:
                dump_obj(elem)
        case tuple() if type(x) == tuple: # <-- Added guard
            emit('T')
            dump_obj(list(x))
        case str() if type(x) == str:     # <-- Added guard
            emit('S')
            dump_obj(len(x))
            emit(x)
        case Iterable():                  # <-- Move after list, tuple, str
            emit('I')
            dump_obj((type(x).__name__, list(x)))
        case int():
            emit('D')
            emit(str(x))
        case _:
            raise TypeError(f'Unknown obj {x!r}')

样本运行

这里我们展示了两个有问题的案例按预期工作。

>>> dump_obj((10, 20))     # Tuple of integers
T
L
D
2
D
10
D
20

>>> class List(list):
...     pass
...
>>> dump_obj(List((30, 40)))   # List subclass
I
T
L
D
2
S
D
4
List
L
D
2
D
30
D
40