PEP 3119 – 引入抽象基类
- 作者:
- Guido van Rossum <guido at python.org>, Talin <viridia at gmail.com>
- 状态:
- 最终版
- 类型:
- 标准跟踪
- 创建日期:
- 2007年4月18日
- Python 版本:
- 3.0
- 发布历史:
- 2007年4月26日, 2007年5月11日
摘要
这是一项关于为 Python 3000 添加抽象基类(ABC)支持的提议。它提出了
- 一种重载
isinstance()和issubclass()的方法。 - 一个名为
abc的新模块,它充当“ABC 支持框架”。它为 ABC 定义了一个元类和一个可用于定义抽象方法的装饰器。 - 用于容器和迭代器的特定 ABC,将添加到 collections 模块中。
本提议的许多思考并非关于 ABC 的具体机制,而是与接口或泛型函数(GF)对比,而是关于澄清哲学问题,例如“什么构成一个集合”,“什么构成一个映射”和“什么构成一个序列”。
还有一个配套的 PEP 3141,它为数字类型定义了 ABC。
致谢
Talin 撰写了下面的“原理” [1] 以及关于 ABC 与接口的大部分章节。仅凭这一点,他就应该获得联合作者的荣誉。PEP 的其余部分使用“我”指代第一作者。
基本原理
在面向对象编程领域,与对象交互的使用模式可以分为两大类,即“调用”和“检查”。
调用意味着通过调用对象的方法来与其交互。通常这与多态结合,因此调用给定方法可能会根据对象的类型运行不同的代码。
检查是指外部代码(对象方法之外)检查该对象的类型或属性,并根据该信息决定如何处理该对象的能力。
这两种用法模式服务于相同的总体目标,即能够以统一的方式支持对各种潜在新颖对象的处理,同时允许为每种不同类型的对象自定义处理决策。
在经典的 OOP 理论中,调用是首选的使用模式,而检查则被积极不鼓励,被认为是早期过程式编程风格的遗留。然而,在实践中,这种观点过于教条和僵化,并导致了一种设计上的僵硬,这与 Python 这种动态语言的性质截然不同。
特别是,经常需要以对象创建者未预料到的方式处理对象。将满足每个潜在用户需求的内置方法添加到每个对象中,并非总是最佳解决方案。此外,许多强大的分派哲学与经典 OOP 要求行为严格封装在对象内部的要求直接相反,例如基于规则或模式匹配的逻辑。
另一方面,经典 OOP 理论家对检查的批评之一是缺乏形式化和检查内容的随意性。在 Python 这样的语言中,对象几乎任何方面都可以被外部代码反射和直接访问,有许多不同的方法来测试一个对象是否符合特定的协议。例如,如果问“这个对象是可变序列容器吗?”,你可以查找“list”的基类,或者查找一个名为“__getitem__”的方法。但请注意,尽管这些测试看似明显,但它们都不是正确的,因为一个会产生假阴性,另一个会产生假阳性。
普遍认同的补救措施是标准化测试,并将它们分组到一个正式的安排中。这可以通过将一组可测试的属性与每个类关联起来,通过继承机制或其他方式来实现。每个测试都带有约定:它包含一个关于类的通用行为的约定,以及一个关于将提供哪些其他类方法的约定。
本 PEP 提出了一种组织这些测试的特定策略,称为抽象基类(ABC)。ABC 仅仅是添加到对象继承树中的 Python 类,用于向外部检查器指示该对象的某些特征。使用 isinstance() 进行测试,并且存在某个 ABC 意味着测试已通过。
此外,ABC 定义了最少数量的方法,这些方法确立了该类型的特征行为。基于其 ABC 类型区分对象的代码可以相信这些方法始终存在。这些方法中的每一个都附带一个广义的抽象语义定义,该定义在 ABC 的文档中进行了描述。这些标准语义定义不强制执行,但强烈建议。
像 Python 中的其他事物一样,这些约定本质上是一种友好的协议,这意味着虽然语言确实强制执行了 ABC 中做出的一些约定,但具体类的实现者有责任确保其余的约定得到遵守。
规范
规范遵循摘要中列出的类别
- 一种重载
isinstance()和issubclass()的方法。 - 一个名为
abc的新模块,它充当“ABC 支持框架”。它为 ABC 定义了一个元类和一个可用于定义抽象方法的装饰器。 - 用于容器和迭代器的特定 ABC,将添加到 collections 模块中。
重载 isinstance() 和 issubclass()
在开发本 PEP 及其配套的 PEP 3141 的过程中,我们反复面临在标准化更细粒度的 ABC 还是更少粒度的 ABC 之间的选择。例如,在某个阶段,PEP 3141 引入了用于复数的以下基类堆栈:MonoidUnderPlus、AdditiveGroup、Ring、Field、Complex(每个都派生自前一个)。讨论中提到了其他几种被排除的代数分类:Algebraic、Transcendental 和 IntegralDomain、PrincipalIdealDomain。在本 PEP 的早期版本中,我们考虑了独立类如 Set、ComposableSet、MutableSet、HashableSet、MutableComposableSet、HashableComposableSet 的用例。
这里的困境是,我们宁愿拥有更少的 ABC,但如果用户需要一个不够精细的 ABC 该怎么办?例如,考虑一位想要定义自己的超越数类型的数学家,但他也希望 float 和 int 被视为超越数。 PEP 3141 最初提出为此目的修补 float.__bases__,但有一些充分的理由可以使内置类型保持不变(例如,它们在同一地址空间中运行的所有 Python 解释器之间共享,如 mod_python 所使用的 [16])。
另一个例子是有人想为一个具有 append() 方法的任何序列定义一个泛型函数(PEP 3124)。Sequence ABC(见下文)不保证 append() 方法,而 MutableSequence 不仅要求 append(),还要求各种其他修改方法。
为了解决这些以及类似的困境,下一节将提出一个用于 ABC 的元类,该元类允许我们将 ABC 添加为“虚拟基类”(与 C++ 中的概念不同),它可以添加到任何类,包括另一个 ABC。这使得标准库可以定义 Sequence 和 MutableSequence ABC,并将它们注册为内置类型(如 basestring、tuple 和 list)的虚拟基类,以便例如以下条件都为真
isinstance([], Sequence)
issubclass(list, Sequence)
issubclass(list, MutableSequence)
isinstance((), Sequence)
not issubclass(tuple, MutableSequence)
isinstance("", Sequence)
issubclass(bytearray, MutableSequence)
此处提出的主要机制是允许重载内置函数 isinstance() 和 issubclass()。重载的工作原理如下:调用 isinstance(x, C) 首先检查 C.__instancecheck__ 是否存在,如果存在,则调用 C.__instancecheck__(x) 而不是其正常实现。类似地,调用 issubclass(D, C) 首先检查 C.__subclasscheck__ 是否存在,如果存在,则调用 C.__subclasscheck__(D) 而不是其正常实现。
请注意,魔法名称不是 __isinstance__ 和 __issubclass__;这是因为参数的颠倒可能会引起混淆,特别是对于 issubclass() 重载。
这里的原型实现 [12]。
这里有一个(天真地简单)实现 __instancecheck__ 和 __subclasscheck__ 的示例
class ABCMeta(type):
def __instancecheck__(cls, inst):
"""Implement isinstance(inst, cls)."""
return any(cls.__subclasscheck__(c)
for c in {type(inst), inst.__class__})
def __subclasscheck__(cls, sub):
"""Implement issubclass(sub, cls)."""
candidates = cls.__dict__.get("__subclass__", set()) | {cls}
return any(c in candidates for c in sub.mro())
class Sequence(metaclass=ABCMeta):
__subclass__ = {list, tuple}
assert issubclass(list, Sequence)
assert issubclass(tuple, Sequence)
class AppendableSequence(Sequence):
__subclass__ = {list}
assert issubclass(list, AppendableSequence)
assert isinstance([], AppendableSequence)
assert not issubclass(tuple, AppendableSequence)
assert not isinstance((), AppendableSequence)
下一节将提出一个完整的实现。
abc 模块:一个 ABC 支持框架
新的标准库模块 abc,用纯 Python 编写,充当 ABC 支持框架。它定义了一个元类 ABCMeta 和装饰器 @abstractmethod 和 @abstractproperty。示例实现由 [13] 提供。
ABCMeta 类重写了 __instancecheck__ 和 __subclasscheck__,并定义了一个 register 方法。register 方法接受一个参数,该参数必须是一个类;在调用 B.register(C) 之后,调用 issubclass(C, B) 将返回 True,因为 B.__subclasscheck__(C) 返回 True。此外,isinstance(x, B) 等价于 issubclass(x.__class__, B) or issubclass(type(x), B)。(type(x) 和 x.__class__ 可能不是同一个对象,例如当 x 是代理对象时。)
这些方法旨在应用于其元类是(派生自)ABCMeta 的类;例如
from abc import ABCMeta
class MyABC(metaclass=ABCMeta):
pass
MyABC.register(tuple)
assert issubclass(tuple, MyABC)
assert isinstance((), MyABC)
最后两个断言等价于以下两个
assert MyABC.__subclasscheck__(tuple)
assert MyABC.__instancecheck__(())
当然,您也可以直接继承 MyABC
class MyClass(MyABC):
pass
assert issubclass(MyClass, MyABC)
assert isinstance(MyClass(), MyABC)
同样,当然,元组不是 MyClass
assert not issubclass(tuple, MyClass)
assert not isinstance((), MyClass)
您可以将另一个类注册为 MyClass 的子类
MyClass.register(list)
assert issubclass(list, MyClass)
assert issubclass(list, MyABC)
您也可以注册另一个 ABC
class AnotherClass(metaclass=ABCMeta):
pass
AnotherClass.register(basestring)
MyClass.register(AnotherClass)
assert isinstance(str, MyABC)
最后一个断言需要跟踪以下超类-子类关系
MyABC -> MyClass (using regular subclassing)
MyClass -> AnotherClass (using registration)
AnotherClass -> basestring (using registration)
basestring -> str (using regular subclassing)
abc 模块还定义了一个新的装饰器 @abstractmethod,用于声明抽象方法。包含至少一个已使用此装饰器声明但尚未被覆盖的方法的类无法实例化。这些方法可以在子类的覆盖方法中调用(使用 super 或直接调用)。例如
from abc import ABCMeta, abstractmethod
class A(metaclass=ABCMeta):
@abstractmethod
def foo(self): pass
A() # raises TypeError
class B(A):
pass
B() # raises TypeError
class C(A):
def foo(self): print(42)
C() # works
注意: @abstractmethod 装饰器只能在类体内部使用,并且只能用于元类是(派生自)ABCMeta 的类。动态地向类添加抽象方法,或尝试在类创建后修改方法的抽象状态,是不支持的。 @abstractmethod 仅影响通过常规继承派生的子类;使用 register() 方法注册的“虚拟子类”不受影响。
实现: @abstractmethod 装饰器将函数属性 __isabstractmethod__ 设置为 True。 ABCMeta.__new__ 方法计算类型属性 __abstractmethods__,它是所有方法名的集合,这些方法名具有值为 True 的 __isabstractmethod__ 属性。它通过合并基类的 __abstractmethods__ 属性,添加新类字典中所有具有 true __isabstractmethod__ 属性的方法名,并移除新类字典中所有不具有 true __isabstractmethod__ 属性的方法名来实现。如果结果的 __abstractmethods__ 集合非空,则该类被视为抽象类,尝试实例化它将引发 TypeError。(如果这是在 CPython 中实现的,内部标志 Py_TPFLAGS_ABSTRACT 可以用来加速此检查 [6]。)
讨论: 与 Java 的抽象方法或 C++ 的纯抽象方法不同,这里定义的抽象方法可以有一个实现。可以通过 super 机制从覆盖它的类调用此实现。这可能有助于在使用协作式多重继承的框架中作为 super 调用的终结点 [7], [8]。
第二个装饰器 @abstractproperty,用于定义抽象数据属性。它是内置 property 类的子类,添加了 __isabstractmethod__ 属性
class abstractproperty(property):
__isabstractmethod__ = True
它可以使用两种方式
class C(metaclass=ABCMeta):
# A read-only property:
@abstractproperty
def readonly(self):
return self.__x
# A read-write property (cannot use decorator syntax):
def getx(self):
return self.__x
def setx(self, value):
self.__x = value
x = abstractproperty(getx, setx)
与抽象方法类似,继承抽象属性(使用装饰器语法或长形式声明)的子类,除非用具体属性覆盖该抽象属性,否则无法实例化。
容器和迭代器的 ABC
collections 模块将定义用于处理集合、映射、序列以及某些辅助类型(如迭代器和字典视图)的 ABC。所有 ABC 都具有上述 ABCMeta 作为其元类。
ABC 提供其抽象方法的实现,这些实现技术上有效但相当无用;例如 __hash__ 返回 0,而 __iter__ 返回一个空迭代器。通常,抽象方法表示指示类型的空容器的行为。
一些 ABC 还提供具体(即非抽象)方法;例如,Iterator 类有一个 __iter__ 方法返回自身,满足了迭代器的一个重要不变量(在 Python 2 中,这需要由每个迭代器类重新实现)。这些 ABC 可以被认为是“混入”类。
PEP 中定义的 ABC 均未重写 __init__、__new__、__str__ 或 __repr__。定义标准的构造函数签名会不必要地限制自定义容器类型,例如 Patricia 树或 gdbm 文件。定义集合的特定字符串表示也是如此,留给各个实现。
注意: 没有用于排序操作(__lt__, __le__, __ge__, __gt__)的 ABC。在基类(抽象或非抽象)中定义这些会导致第二个操作数的类型出现问题。例如,如果类 Ordering 定义了 __lt__,那么对于任何 Ordering 实例 x 和 y,x < y 将被定义(即使它只定义了部分排序)。但这不可能:如果 list 和 str 都派生自 Ordering,这将意味着 [1, 2] < (1, 2) 应该被定义(并可能返回 False),而实际上(在 Python 3000 中!)这种“混合模式比较”操作是明确禁止的,并且会引发 TypeError。有关更多信息,请参阅 PEP 3100 和 [14]。(这是对接受相同类型另一个参数的操作的更一般问题的特殊情况。)
单打独斗
这些抽象类代表单个方法,如 __iter__ 或 __len__。
Hashable- 定义
__hash__的类的基类。__hash__方法应返回一个整数。抽象的__hash__方法始终返回 0,这是一个有效(尽管效率不高)的实现。不变量: 如果类C1和C2都派生自Hashable,则条件o1 == o2必须意味着对于C1的所有实例o1和C2的所有实例o2,hash(o1) == hash(o2)。换句话说,两个对象永远不应在具有不同哈希值时进行相等比较。另一个约束是,可哈希对象一旦创建,其值(由
==比较)或其哈希值不应改变。如果类无法保证这一点,它就不应派生自Hashable;如果它无法保证某些实例这一点,则这些实例的__hash__应引发TypeError异常。注意: 成为此类的一个实例并不意味着该对象是不可变的;例如,包含列表作为成员的元组不是不可变的;其
__hash__方法引发TypeError。(这是因为它可以递归地尝试计算每个成员的哈希值;如果成员不可哈希,它会引发TypeError。) Iterable- 定义
__iter__的类的基类。__iter__方法应始终返回Iterator的实例(见下文)。抽象的__iter__方法返回一个空迭代器。 Iterator- 定义
__next__的类的基类。它派生自Iterable。抽象的__next__方法引发StopIteration。具体的__iter__方法返回self。注意Iterable和Iterator之间的区别:Iterable可以被迭代,即支持__iter__方法;Iterator是内置函数iter()返回的内容,即支持__next__方法。 Sized- 定义
__len__的类的基类。__len__方法应返回一个 >= 0 的Integer(见下文“数字”)。抽象的__len__方法返回 0。不变量: 如果类C派生自Sized以及Iterable,那么对于C的任何实例c,都应满足sum(1 for x in c) == len(c)。 Container- 定义
__contains__的类的基类。__contains__方法应返回一个bool。抽象的__contains__方法返回False。不变量: 如果类C派生自Container以及Iterable,那么对于C的任何实例c,(x in c for x in c)应该是一个只产生 True 值的生成器。
未解决的问题: 概念上,这些类可以重写 __instancecheck__ 和 __subclasscheck__ 来检查适用特殊方法的存在,而不是使用 ABCMeta 元类;例如
class Sized(metaclass=ABCMeta):
@abstractmethod
def __hash__(self):
return 0
@classmethod
def __instancecheck__(cls, x):
return hasattr(x, "__len__")
@classmethod
def __subclasscheck__(cls, C):
return hasattr(C, "__bases__") and hasattr(C, "__len__")
这有不要求显式注册的优点。但是,考虑到实例属性与类属性的混淆语义,以及类是其元类的实例,语义很难完全正确;对 __bases__ 的检查只是期望语义的近似值。临时方案: 我们这样做,但我们要以一种也允许注册 API 工作的方式进行安排。
集合
这些抽象类代表只读集合和可变集合。最基本的集合操作是成员资格测试,写为 x in s,由 s.__contains__(x) 实现。此操作已由上面的 Container 类定义。因此,我们将集合定义为一种有大小、可迭代的容器,其中满足数学集合论的某些不变量。
内置类型 set 派生自 MutableSet。内置类型 frozenset 派生自 Set 和 Hashable。
Set- 这是一个有大小、可迭代的容器,即
Sized、Iterable和Container的子类。并非所有这三个类的子类都是集合!集合还具有每个元素只出现一次(可通过迭代确定)的附加不变量,此外,集合定义了具体的运算符,这些运算符将不等式运算实现为子集/超集测试。一般来说,数学中的有限集的不变量成立。 [11]可以使用数学定义对具有不同实现的集合进行安全、(通常)高效且正确地比较(有限集的子集/超集运算)。排序操作具有具体的实现;子类可以为了速度而覆盖它们,但应保持语义。因为
Set派生自Sized,所以在比较两个长度不相等的集合时,__eq__可以采取捷径并立即返回False。类似地,如果第一个集合的成员数多于第二个集合,__le__可以立即返回False。请注意,集合包含关系仅实现部分排序;例如,{1, 2}和{1, 3}没有排序(所有<、==和>都对这些参数返回False)。集合不能与映射或序列排序,但它们可以为了相等性进行比较(然后它们总是比较不相等)。此类还定义了具体的运算符来计算并集、交集、对称差和非对称差,分别是
__or__、__and__、__xor__和__sub__。这些运算符应返回Set的实例。默认实现调用可覆盖的类方法_from_iterable()并传递一个可迭代参数。此工厂方法的默认实现返回一个frozenset实例;它可以被覆盖以返回另一个合适的Set子类。最后,此类定义了一个具体的
_hash方法,该方法从元素计算哈希值。Set的可哈希子类可以通过调用_hash来实现__hash__,或者它们可以更高效地重新实现相同的算法;但实现的算法应相同。目前,算法仅由源代码 [15] 完全指定。注意: Python 2 中的集合类型上的
issubset和issuperset方法不被支持,因为它们大部分只是__le__和__ge__的别名。 MutableSet- 这是
Set的一个子类,实现了用于添加和删除元素的其他操作。支持的方法具有 Python 2 中set类型已知的语义(discard除外,它模仿 Java).add(x)- 抽象方法,返回一个
bool,如果元素x不在集合中,则添加它。它应该返回True如果x被添加,False如果它已存在。抽象实现引发NotImplementedError。 .discard(x)- 抽象方法,返回一个
bool,如果元素x存在,则删除它。它应该返回True如果元素存在,False如果不存在。抽象实现引发NotImplementedError。 .pop()- 具体方法,删除并返回一个任意项。如果集合为空,则引发
KeyError。默认实现删除集合迭代器返回的第一个项。 .toggle(x)- 具体方法,返回一个
bool,如果x不在集合中,则添加它,但如果它在集合中,则删除它。它应该返回True如果x被添加,False如果它被删除。 .clear()- 具体方法,清空集合。默认实现反复调用
self.pop()直到捕获到KeyError。(注意: 这可能比创建一个新集合慢得多,即使实现了一个更快的覆盖方法;但在某些情况下,对象标识很重要。)
它还支持就地修改操作
|=、&=、^=、-=。这些是具体方法,其右侧操作数可以是任意Iterable,除了&=,其右侧操作数必须是Container。此 ABC 不提供内置具体set类型上存在的命名方法,这些方法执行(几乎)相同的操作。
映射
这些抽象类代表只读映射和可变映射。 Mapping 类代表最常见的只读映射 API。
内置类型 dict 派生自 MutableMapping。
Mapping- 一个
Container、Iterable和Sized的子类。映射的键自然构成一个集合。键值对(必须是元组)也称为项。项也构成一个集合。方法.__getitem__(key)- 抽象方法,返回与
key对应的value,或引发KeyError。实现总是引发KeyError。 .get(key, default=None)- 具体方法,如果
self[key]不引发KeyError,则返回self[key],否则返回default值。 .__contains__(key)- 具体方法,如果
self[key]不引发KeyError,则返回True,否则返回False。 .__len__()- 抽象方法,返回不同键的数量(即键集的大小)。
.__iter__()- 抽象方法,返回键集中的每个键恰好一次。
.keys()- 具体方法,返回键集作为
Set。默认的具体实现返回键集的“视图”(意味着如果底层映射被修改,视图的值会相应更改);子类不一定需要返回视图,但它们应该返回一个Set。 .items()- 具体方法,返回项作为
Set。默认的具体实现返回项集的“视图”;子类不一定需要返回视图,但它们应该返回一个Set。 .values()- 具体方法,返回值作为有大小、可迭代的容器(不是集合!)。默认的具体实现返回映射值的“视图”;子类不一定需要返回视图,但它们应该返回一个有大小、可迭代的容器。
对于任何映射
m,以下不变量应成立len(m.values()) == len(m.keys()) == len(m.items()) == len(m) [value for value in m.values()] == [m[key] for key in m.keys()] [item for item in m.items()] == [(key, m[key]) for key in m.keys()]
即迭代项、键和值应该按相同的顺序返回结果。
MutableMapping- 一个
Mapping的子类,它还实现了标准的修改方法。抽象方法包括__setitem__、__delitem__。具体方法包括pop、popitem、clear、update。注意:setdefault*不*包含在内。未解决的问题: 写出方法的规范。
序列
这些抽象类代表只读序列和可变序列。
内置的 list 和 bytes 类型派生自 MutableSequence。内置的 tuple 和 str 类型派生自 Sequence 和 Hashable。
Sequence- 一个
Iterable、Sized、Container的子类。它定义了一个新的抽象方法__getitem__,该方法具有一个稍微复杂但签名:当使用整数调用时,它返回序列的元素或引发IndexError;当使用slice对象调用时,它返回另一个Sequence。具体的__iter__方法通过使用整数参数 0、1 等,使用__getitem__来迭代元素,直到引发IndexError。长度应等于迭代器返回的值的数量。未解决的问题: 其他候选方法,这些方法都可以具有仅依赖于
__len__和带有整数参数的__getitem__的默认具体实现:__reversed__、index、count、__add__、__mul__。 MutableSequence- 一个
Sequence的子类,添加了一些标准的修改方法。抽象的修改方法:__setitem__(用于整数索引和切片)、__delitem__(同上)、insert。具体的修改方法:append、reverse、extend、pop、remove。具体的修改运算符:+=、*=(这些就地修改对象)。注意: 这没有定义sort()- 这只要求存在于真正的list实例上。
字符串
Python 3000 可能至少有两个内置字符串类型:字节字符串(bytes),派生自 MutableSequence,以及(Unicode)字符字符串(str),派生自 Sequence 和 Hashable。
未解决的问题: 定义这些的基接口,以便替代实现和子类知道它们将面临什么。这可能是新 PEP 或 PEP 的主题(PEP 358 应被用于 bytes 类型)。
ABC 与替代方案
在本节中,我将尝试比较和对比 ABC 与其他已提出的方法。
ABC 与鸭子类型
引入 ABC 是否意味着鸭子类型的终结?我认为不是。当一个类定义了 __getitem__ 方法时,Python 不会要求它派生自 BasicMapping 或 Sequence,也不会 x[y] 语法要求 x 是其中任何一个 ABC 的实例。只要它有一个 write 方法,您仍然可以将任何“类文件”对象分配给 sys.stdout。
当然,会有一些“胡萝卜”来鼓励用户派生自适当的基类;这些从某些功能的默认实现到区分映射和序列的改进能力不等。但没有“大棒”。如果 hasattr(x, "__len__") 对您有用,那太好了!ABC 旨在解决 Python 2 中根本没有良好解决方案的问题,例如区分映射和序列。
ABC 与泛型函数
ABC 与泛型函数(GF)兼容。例如,我自己的泛型函数实现 [4] 使用参数的类(类型)作为分派键,允许派生类覆盖基类。由于(从 Python 的角度来看)ABC 是相当普通的类,在 GF 的默认实现中使用 ABC 可能非常合适。例如,如果我有一个重载的 prettyprint 函数,定义集合的漂亮打印如下将是很有意义的
@prettyprint.register(Set)
def pp_set(s):
return "{" + ... + "}" # Details left as an exercise
并且可以轻松添加特定 Set 子类的实现。
我相信 ABC 也不会给 RuleDispatch,Phillip Eby 在 PEAK 中的 GF 实现 [5] 带来任何问题。
当然,GF 的支持者可能会声称 GF(以及具体类或实现类)就是您所需要的。但即使是他们也不会否认继承的有用性;并且可以轻松地将本 PEP 中提出的 ABC 视为可选的实现基类;没有要求所有用户定义的映射都派生自 BasicMapping。
ABC 与接口
ABC 与接口在本质上并不不兼容,但存在相当大的重叠。目前,我将留给接口的支持者来解释为什么接口更好。我预计,例如,在定义“映射性”的各种细微差别和命名法方面所做的许多工作,可以很容易地改编为使用接口而不是 ABC 的提案。
“接口”在此上下文中指的是一组关于附加到类的元数据元素的提案,这些元素不属于常规类层次结构,但允许进行某些类型的继承测试。
这些元数据将被设计成,至少在某些提案中,可以被应用程序轻松修改,从而允许应用程序编写者覆盖对象的正常分类。
这种将可变元数据附加到类的想法的缺点是类是共享状态,修改它们可能会导致意图冲突。此外,使用泛型函数可以更干净地覆盖对象的分类:在最简单的情况下,可以定义一个“类别成员资格”泛型函数,该函数在默认实现中简单地返回 False,然后提供返回 True 的覆盖,以用于任何感兴趣的类。
参考资料
[2] GvR 的不完整实现原型(https://web.archive.org/web/20170223133820/http://svn.python.org/view/sandbox/trunk/abc/)
[3] Bill Janssen 创建的 wiki 页面“可能的 Python 3K 类树?”(https://wiki.python.org/moin/AbstractBaseClasses)
[9] 维基百科上的“偏序”(https://en.wikipedia.org/wiki/Partial_order)
[10] 维基百科上的“全序”(https://en.wikipedia.org/wiki/Total_order)
frozenset_hash()(https://web.archive.org/web/20170224204758/http://svn.python.org/view/python/trunk/Objects/setobject.c)版权
本文档已置于公共领域。
来源:https://github.com/python/peps/blob/main/peps/pep-3119.rst