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 等语言中,外部代码几乎可以反映和直接访问对象的任何方面,有很多不同的方法来测试对象是否符合特定协议。例如,如果询问“此对象是否为可变序列容器?”,可以查找“列表”的基类,或者可以查找名为“__getitem__”的方法。但请注意,尽管这些测试看起来很明显,但它们都不正确,因为一个会产生假阴性,另一个会产生假阳性。
普遍认可的补救措施是标准化测试,并将它们组合成正式的安排。这最容易通过将一组标准的可测试属性与每个类关联来完成,无论是通过继承机制还是其他方式。每个测试都带有一组承诺:它包含关于类的一般行为的承诺,以及关于哪些其他类方法可用的承诺。
本 PEP 提出了一种组织这些测试的特定策略,称为抽象基类或 ABC。ABC 只是添加到对象继承树中的 Python 类,以便向外部检查器发出该对象的某些特征的信号。测试是使用 isinstance()
完成的,并且特定 ABC 的存在意味着测试已通过。
此外,ABC 定义了一组最少的方法,这些方法建立了类型的特征行为。根据其 ABC 类型区分对象的代码可以相信这些方法始终存在。这些方法中的每一个都附带一个广义的抽象语义定义,该定义在 ABC 的文档中进行了描述。这些标准语义定义未强制执行,但强烈建议使用。
与 Python 中的所有其他事物一样,这些承诺本质上是一种友好协议,在这种情况下,这意味着虽然语言确实强制执行了 ABC 中做出的一些承诺,但具体类的实现者有责任确保其余承诺得到遵守。
规范
规范遵循摘要中列出的类别
- 一种重载
isinstance()
和issubclass()
的方法。 - 一个新的模块
abc
,作为“ABC 支持框架”。它定义了一个用于 ABC 的元类,以及一个可用于定义抽象方法的装饰器。 - 用于容器和迭代器的特定 ABC,将添加到 collections 模块中。
重载 isinstance()
和 issubclass()
在开发本 PEP 及其配套 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 该怎么办?例如,考虑一下想要定义自己的超越数类型的数学家的困境,但又希望浮点数和整数也被视为超越数。 PEP 3141 最初建议为此目的修补 float.__bases__,但有一些充分的理由来保持内置类型不变(其中之一是它们在同一地址空间中所有 Python 解释器之间共享,如 mod_python [16] 使用的那样)。
另一个例子是有人想要为任何具有 append()
方法的序列定义泛型函数 (PEP 3124)。 Sequence
ABC(见下文)不承诺 append()
方法,而 MutableSequence
不仅需要 append()
,还需要各种其他变异方法。
为了解决这些和类似的难题,下一节将提出一个用于 ABC 的元类,该元类将允许我们将 ABC 作为“虚拟基类”(与 C++ 中的概念不同)添加到任何类中,包括另一个 ABC 中。这允许标准库定义 ABC Sequence
和 MutableSequence
并将其注册为内置类型(如 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
机制从重写它的类中调用此实现。这对于使用协作多重继承的框架中的超级调用的终结点可能很有用 [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 可以被视为“mixin”类。
PEP 中定义的任何 ABC 都不重写 __init__
、__new__
、__str__
或 __repr__
。定义标准的构造函数签名将不必要地限制自定义容器类型,例如 Patricia 树或 gdbm 文件。为集合定义特定的字符串表示形式也留给各个实现。
注意:没有用于排序操作的 ABC(__lt__
、__le__
、__ge__
、__gt__
)。在基类(抽象或非抽象)中定义这些操作会导致第二个操作数的接受类型出现问题。例如,如果类 Ordering
定义了 __lt__
,人们会假设对于任何 Ordering
实例 x
和 y
,x < y
将被定义(即使它只定义了偏序)。但这不可能:如果 list
和 str
都派生自 Ordering
,则这意味着 [1, 2] < (1, 2)
应该被定义(并且可能返回 False),而实际上(在 Python 3000 中!)此类“混合模式比较”操作被明确禁止并引发 TypeError
。有关更多信息,请参阅 PEP 3100 和 [14]。(这是具有采用相同类型参数的操作的更普遍问题的特例。)
单一功能类
这些抽象类表示单个方法,例如 __iter__
或 __len__
。
可散列
- 定义
__hash__
方法的类的基类。__hash__
方法应返回一个整数。抽象__hash__
方法始终返回 0,这是一个有效的(尽管效率低下)实现。不变式:如果类C1
和C2
都派生自Hashable
,则条件o1 == o2
必须意味着hash(o1) == hash(o2)
,对于C1
的所有实例o1
和C2
的所有实例o2
。换句话说,如果两个对象的哈希值不同,则它们不应该比较相等。另一个约束是,可散列对象一旦创建,就不应该更改其值(通过
==
比较)或其哈希值。如果一个类无法保证这一点,它不应该派生自Hashable
;如果它无法保证某些实例的这一点,则这些实例的__hash__
应该引发TypeError
异常。注意:作为此类的实例并不意味着对象是不可变的;例如,包含列表作为成员的元组不是不可变的;它的
__hash__
方法引发TypeError
。(这是因为它递归地尝试计算每个成员的哈希值;如果成员不可散列,则引发TypeError
。) 可迭代
- 定义
__iter__
方法的类的基类。__iter__
方法应该始终返回Iterator
的实例(见下文)。抽象__iter__
方法返回一个空迭代器。 迭代器
- 定义
__next__
方法的类的基类。它继承自Iterable
。抽象的__next__
方法会抛出StopIteration
异常。具体的__iter__
方法返回self
。请注意Iterable
和Iterator
之间的区别:Iterable
可以被迭代,即支持__iter__
方法;Iterator
是内置函数iter()
返回的对象,即支持__next__
方法。 Sized
- 定义
__len__
方法的类的基类。__len__
方法应该返回一个Integer
(参见下面的“数字”)>= 0。抽象的__len__
方法返回 0。**不变式:**如果类C
继承自Sized
以及Iterable
,则不变式sum(1 for x in c) == len(c)
应该对C
的任何实例c
成立。 Container
- 定义
__contains__
方法的类的基类。__contains__
方法应该返回一个bool
值。抽象的__contains__
方法返回False
。**不变式:**如果类C
继承自Container
以及Iterable
,则(x in c for x in c)
应该是一个生成器,它对C
的任何实例c
只产生 True 值。
未解决的问题:可以设想,与其使用 ABCMeta 元类,不如重写 __instancecheck__
和 __subclasscheck__
来检查是否存在适用的特殊方法;例如
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
不在集合中,则将其添加到集合中。如果添加了x
,则应返回True
;如果它已经存在,则返回False
。抽象实现会抛出NotImplementedError
异常。 .discard(x)
- 抽象方法返回一个
bool
值,如果元素x
存在,则将其从集合中删除。如果元素存在,则应返回True
;如果不存在,则返回False
。抽象实现会抛出NotImplementedError
异常。 .pop()
- 具体方法,删除并返回任意一个项目。如果集合为空,则抛出
KeyError
异常。默认实现删除集合迭代器返回的第一个项目。 .toggle(x)
- 具体方法返回一个
bool
值,如果x
不在集合中,则将其添加到集合中;如果它在集合中,则将其删除。如果添加了x
,则应返回True
;如果将其删除,则返回False
。 .clear()
- 具体方法,清空集合。默认实现重复调用
self.pop()
,直到捕获到KeyError
异常。(**注意:**即使实现使用更快的方案覆盖它,这可能也比简单地创建一个新集合慢得多;但在某些情况下,对象标识很重要。)
它还支持就地修改操作
|=
、&=
、^=
、-=
。这些是具体方法,其右操作数可以是任意Iterable
,除了&=
,其右操作数必须是Container
。此 ABC 没有提供内置具体set
类型上存在的执行(几乎)相同操作的命名方法。
映射
这些抽象类表示只读映射和可变映射。 Mapping
类表示最常见的只读映射 API。
内置类型 dict
继承自 MutableMapping
。
Mapping
Container
、Iterable
和Sized
的子类。映射的键自然形成一个集合。(键,值)对(必须是元组)也称为项目。项目也形成一个集合。方法.__getitem__(key)
- 返回对应于
key
的值的抽象方法,或引发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 的引入是否意味着鸭子类型的终结?我不这么认为。当 Python 定义__getitem__
方法时,它不会要求一个类派生自BasicMapping
或Sequence
,也不会要求x[y]
语法要求x
是任一 ABC 的实例。您仍然可以将任何“类文件”对象分配给sys.stdout
,只要它具有write
方法即可。
当然,会有一些诱因来鼓励用户从相应的基类派生;这些诱因从某些功能的默认实现到区分映射和序列的改进能力不等。但没有强制要求。如果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 带来任何问题,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] 可能的 Python 3K 类树?,由 Bill Janssen 创建的维基页面 (https://wiki.python.org/moin/AbstractBaseClasses)
[9] 部分序,在维基百科中 (https://en.wikipedia.org/wiki/Partial_order)
[10] 全序,在维基百科中 (https://en.wikipedia.org/wiki/Total_order)
frozenset_hash()
在 Object/setobject.c 中 (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
最后修改时间:2023-09-09 17:39:29 GMT