文章摘要
Python 3.10引入的模式匹配功能与抽象基类(ABC)的__subclasshook__方法结合,可能导致一些意想不到的行为。通过__subclasshook__,可以自定义类的子类判定逻辑,例如判断类名是否为回文。虽然之前这种机制在Python中并未造成严重问题,但模式匹配的加入可能带来新的复杂性,尤其是在处理非单调类型时,如判断一个类是否不具备__iter__方法。这种组合可能在某些情况下引发难以预料的结果。
文章总结
Python 模式匹配中的“犯罪”
Python 中的 __subclasshook__ 是一个有趣的功能,它允许抽象基类(ABC)定义哪些类可以被视为其子类,即使目标类并不知道该 ABC 的存在。例如,可以定义一个 PalindromicName 类,通过 __subclasshook__ 判断类名是否为回文:
```python class PalindromicName(ABC): @classmethod def subclasshook(cls, C): name = C.name.lower() return name[::-1] == name
class Abba: ... class Baba: ...
isinstance(Abba(), PalindromicName) True isinstance(Baba(), PalindromicName) False ```
这种机制可以用来实现一些奇怪的功能,比如创建“非单调类型”,即某个类如果没有 __iter__ 方法,则被视为 NotIterable。然而,Python 3.10 引入了模式匹配功能,这为 __subclasshook__ 带来了新的可能性。
模式匹配简介
模式匹配允许根据对象的类型、结构等进行匹配。例如:
python
match command.split():
case ["quit"]:
print("Goodbye!")
quit_game()
case ["look"]:
current_room.describe()
case ["get", obj]:
character.get(obj, current_room)
在匹配对象时,Python 使用 isinstance(obj, class) 进行检查,包括判断对象是否为类的子类,或者是否满足 ABC 的 __subclasshook__ 条件。这让人不禁思考:ABC 是否可以“劫持”模式匹配?
ABC 劫持模式匹配
通过定义 NotIterable ABC,可以尝试在模式匹配中实现非迭代对象的判断:
```python class NotIterable(ABC): @classmethod def subclasshook(cls, C): return not hasattr(C, "iter")
def f(x): match x: case NotIterable(): print(f"{x} is not iterable") case _: print(f"{x} is iterable")
f(10) # 输出:10 is not iterable f("string") # 输出:string is iterable f([1, 2, 3]) # 输出:[1, 2, 3] is iterable ```
令人惊讶的是,这种“劫持”确实有效。
进一步扩展
模式匹配还可以解构对象的字段。例如,匹配具有 distance 属性的对象:
```python class DistanceMetric(ABC): @classmethod def subclasshook(cls, C): return hasattr(C, "distance")
def f(x): match x: case DistanceMetric(distance=d): print(d) case _: print(f"{x} is not a point")
f(Point2D(10, 10)) # 输出:14.142135623730951 f(Point3D(5, 6, 7)) # 输出:10.488088481701515 f([1, 2, 3]) # 输出:[1, 2, 3] is not a point ```
甚至可以根据对象的字段值进行更复杂的匹配:
python
def f(x):
match x:
case DistanceMetric(z=3):
print(f"A point with a z-coordinate of 3")
case DistanceMetric(z=z):
print(f"A point with a z-coordinate that's not 3")
case DistanceMetric():
print(f"A point without a z-coordinate")
case _:
print(f"{x} is not a point")
组合器
虽然模式匹配功能强大,但也有一些限制。例如,它只能基于对象的类型进行匹配。为了绕过这一限制,可以利用 Python 的动态类型特性,在运行时创建新的 ABC:
```python def Not(cls): class Not(ABC): @classmethod def subclasshook(, C): return not issubclass(C, cls) return _Not
def f(x): n = Not(DistanceMetric) match x: case n(): print(f"{x} is not a point") case _: print(f"{x} is a point") ```
通过这种方式,可以组合多个条件进行匹配:
```python def And(cls1, cls2): class And(ABC): @classmethod def subclasshook(, C): return issubclass(C, cls1) and issubclass(C, cls2) return _And
def f(x): n = And(Iterable, Not(str)) match x: case n(): print(f"{x} is a non-string iterable") case str(): print(f"{x} is a string") case _: print(f"{x} is a string or not-iterable") ```
副作用与缓存
尽管 __subclasshook__ 可以用于实现一些有趣的功能,但它也有一些限制。例如,CPython 会缓存 __subclasshook__ 的结果,因此无法通过它实现基于副作用的匹配。不过,仍然可以通过其他方式实现一些有趣的效果,例如让 ABC 交替匹配不同的类型:
```python class FlipFlop(ABC): flag = False
@classmethod
def __subclasshook__(cls, _):
cls.flag = not cls.flag
return cls.flag
```
结论
虽然 __subclasshook__ 和模式匹配的结合可以实现一些非常规的功能,但这种“黑魔法”并不适合在实际生产代码中使用。模式匹配功能本身设计合理,而 __subclasshook__ 则过于复杂和难以预测。因此,除非在极端情况下,否则不建议使用这种技巧。
总之,这篇文章展示了 Python 中一些有趣但危险的特性,提醒我们在使用这些功能时要谨慎。
评论总结
评论内容主要围绕Python的模式匹配设计展开,观点多样,既有批评也有认可。
批评观点:
1. 模式匹配设计不清晰:评论1指出Python的模式匹配语法存在歧义,例如case foo.bar和case foo的行为不一致,且无法直接匹配整个对象。引用:“case foo.bar is a value match, but case foo is a name capture.” 评论4进一步批评了模式匹配的设计,指出常量命名在不同上下文中的行为不一致。引用:“the first checks for equality (status == 404) and the second performs an assignment (not_found = status).”
- 增加代码复杂性:评论2和评论3认为模式匹配增加了代码的复杂性,使其难以阅读和理解。引用:“it adds complexity and makes code harder to read in a goto-style way.”
认可观点: 1. 类型检查的确定性:评论5认为Python的类型检查是确定性的,问题并不严重。引用:“Barely a misdemeanor, all of the typechecks were deterministic.”
- 原型设计中的实用性:评论7提到Python的某些非惯用特性(如
__rrshift__)在原型设计中非常有用,尤其是在交互式环境中。引用:“Actually useful for prototyping? 1000%.”
其他观点:
评论6提到Python近期的设计决策(如使用{/}表示空集)引发争议,认为这些设计越来越令人怀疑。引用:“More and more dubious things were designed in Python these days.”
总结:评论者对Python模式匹配的设计存在较大分歧,批评者认为其语法不清晰且增加代码复杂性,而支持者则认为其在某些场景下具有实用价值。