2007年,Python 添加了
抽象基类
,旨在用作接口:
Back in 2007 Python added Abstract Base Classes, which were intended to be used as interfaces:
from abc import ABC
class AbstractIterable(ABC):
@abstractmethod
def __iter__(self):
while False:
yield None
def get_iterator(self):
return self.__iter__()
ABC 被添加来稍微增强
鸭子类型
。如果你继承了 AbstractIterable
,那么所有人都能知道你有一个实现了的 __iter__
方法,可以适当地对其处理。
ABCs were added to strengthen the duck typing a little. If you inherited AbstractIterable, then everybody knew you had an implemented __iter__ method, and could handle that appropriately.
不出所料,这个想法从未流行起来。人们更喜欢“请求原谅而不是请求许可”,然后将对 __iter__
的调用包装在 try
块中。这对静态类型检查可以很有用,但实际上 Mypy 并不使用它。如果你想进行类型检查,发现它有 __iter__
,但某人没有从 AbstractIterable
继承,该怎么办?Mypy 团队改用
协议
,它由 ABC
引导
,但对用户隐藏了这个细节。
Unsurprisingly, this idea never caught on. People instead preferred “better ask forgiveness than permission” and wrapped calls to __iter__ in a try block. This could be useful for static type checking, but in practice Mypy doesn’t use it. What if you wanted to typecheck it had __iter__ but the person did not inherit from AbstractIterable? The Mypy team instead uses protocols, which is bootstrapped off ABCs but hides that detail from the user.
但 ABC 旨在
向后兼容
。而且已经存在具有 __iter__
方法的类。我们如何将它们纳入我们的 AbstractIterable
ABC 中?为了解决这个问题,Python 团队添加了一个特殊的 ABC 方法:
But ABC was intended to be backwards compatible. And there were already existing classes that had a iter method. How could we include them under our AbstractIterable ABC? To handle this, the Python team added a special ABC method:
class AbstractIterable(ABC):
@classmethod
def __subclasshook__(cls, C):
return hasattr(C, "__iter__")
__subclasshook__
是使某些类算作这个 ABC 的子类的运行时条件。如果 OurClass
具有 __iter__
属性,那么 isinstance(OurClass(), AbstractIterable)
就为真,即使它不是从 AbstractIterable
继承的。
__subclasshook__ is the runtime conditions that makes something count as a child of this ABC. isinstance(OurClass(), AbstractIterable) is true if OurClass has a iter attribute, even if it didn’t inherit from AbstractIterable.
这个函数是一个运行时函数。我们可以在其中放入任意代码。它传入的是对象的类,而不是对象本身,因此我们无法检查特定对象的
属性
。但我们仍然可以做一些奇怪的事情:
That function is a runtime function. We can put arbitrary code in it. It passes in the object’s class, not the object itself, so we can’t inspect the properties of the specific object. We can still do some weird stuff:
class PalindromicName(ABC):
@classmethod
def __subclasshook__(cls, C):
name = C.__name__.lower()
return name[::-1] == name
任何名字是回文的类,像是“Noon”,都将被视为 PalindromicName
的子类。我们可以更进一步:既然可以跳下去,为什么还要
凝视深渊
呢?
Any class with a palindromic name, like “Noon”, will counts as a child class of PalindromicName. We can push this even further: why gaze into the abyss when you can jump in?
class NotIterable(ABC):
@classmethod
def __subclasshook__(cls, C):
return not hasattr(C, "__iter__")
这是所有不可迭代的类型。比如 isinstance(5, NotAString)
之类的。我们创建了一个
负类型
:一种仅指定它不是什么的类型。我们可以将其作为
正类型
集减去给定类型的子集,剩下的那部分。没有什么可以阻止我们为“不是字符串的可迭代对象”或“返回的对象与同一可调用对象不相同的可调用对象”创建 ABC。
This is the type of everything that isn’t iterable. We have isinstance(5, NotAString), etc. We’ve created a negatype: a type that only specifies what it isn’t. We can include this as part of a set of positive types, subtracting out a subset of a given type. There’s nothing stopping us from making an ABC for “iterables that aren’t strings”, or “callable objects that don’t return an object of the same callable”.