PEP 245 – Python 接口语法
- 作者:
- Michel Pelletier <michel at users.sourceforge.net>
- 状态:
- 已拒绝
- 类型:
- 标准跟踪
- 创建日期:
- 2001年1月11日
- Python 版本:
- 2.2
- 发布历史:
- 2001年3月21日
注意
最初链接到此处的、不再可用的 Zope 接口维基页面 (https://www.zope.org/Wikis/Interfaces),其中包含此 PEP 的更多资源链接,可以在 Wayback Machine 存档上找到。此外,讨论此 PEP 的 Interface-Dev Zope 邮件列表已关闭,但 其存档仍然可用。
拒绝通知
我驳回此 PEP。五年过去了。虽然在某个时候我预计 Python 会有接口,但期望它与此 PEP 中的语法相似是天真的。此外,PEP 246 正在被驳回,取而代之的是完全不同的东西;接口不会在适配或任何替代它的东西中发挥作用。GvR。
引言
本 PEP 描述了一种在 Python 中创建接口对象的拟议语法。
概述
除了考虑向 Python 添加静态类型系统之外,Types-SIG 还负责为 Python 设计一个接口系统。1998 年 12 月,Jim Fulton 发布了一个基于 SIG 讨论的原型接口系统。关于此讨论和原型的许多问题和背景信息可以在 SIG 存档中找到 [1]。
大约在 2000 年底,Digital Creations 开始思考 Zope [2] 的更好的组件模型设计。Zope 未来的组件模型严重依赖接口对象。这导致 Jim 的“稻草人”接口原型得到进一步开发。从 2.3 版本开始,Zope 附带一个 Interface 包作为标准软件。Zope 的 Interface 包被用作此 PEP 的参考实现。
本 PEP 提出的语法依赖于 PEP 232 中描述的语法增强,并描述了一个 PEP 233 可以基于的基础框架。关于接口对象和代理对象有一些工作正在进行,因此对于此 PEP 的那些可选部分,您可能需要参阅 [3]。
问题
接口之所以重要,是因为它们解决了在软件开发过程中出现的许多问题。
- Python 中有许多隐含的接口,通常被称为“协议”。目前,确定这些协议是基于实现内省的,但通常也会失败。例如,定义
__getitem__意味着既是序列又是映射(前者带有顺序的整数键)。开发人员无法明确对象打算实现哪些协议。 - 从开发人员的角度来看,Python 受到类型和类之间分离的限制。当期望类型时,消费者使用诸如“type(foo) == type("")”之类的代码来确定“foo”是否是字符串。当期望类的实例时,消费者使用“isinstance(foo, MyString)”来确定“foo”是否是“MyString”类的实例。没有统一的模型来确定对象是否可以以某种有效的方式使用。
- Python 的动态类型非常灵活和强大,但它没有提供类型检查的静态类型语言的优势。静态类型语言为您提供更高的类型安全性,但通常过于冗长,因为对象只能通过共同的子类化进行泛化,并通过强制转换专门使用(例如,在 Java 中)。
接口还试图解决一些文档问题。
- 开发人员浪费大量时间查看系统源代码以了解对象如何工作。
- 不熟悉系统的新开发人员可能会误解对象的工作方式,导致并可能传播使用错误。
- 由于缺乏接口意味着使用是从源代码推断出来的,开发人员最终可能会使用旨在“仅供内部使用”的方法和属性。
- 代码审查可能很困难,对于试图正确理解大师编写的代码的新手程序员来说非常令人沮丧。
- 当许多人非常努力地理解晦涩难懂的东西(例如未文档化的软件)时,会浪费大量时间。预先花时间记录接口最终会节省大量时间。
接口试图通过提供一种方式来解决这些问题,即指定对象的合同义务、如何使用对象的文档以及发现合同和文档的内置机制。
Python 具有非常有用的自省功能。众所周知,这使得在交互式解释器中探索概念更容易,因为 Python 让您能够查看关于对象的各种信息:类型、文档字符串、实例字典、基类、未绑定方法等等。
这些功能中的许多都面向自省、使用和改变软件的实现,其中一个(“文档字符串”)面向提供文档。本提案描述了对这个自然自省框架的扩展,该框架描述了对象的接口。
接口语法概述
在很大程度上,接口的语法与类的语法非常相似,但未来的需求或讨论中提出的需求可能会为接口语法定义新的可能性。
本 PEP 后文给出了语法的正式 BNF 描述,为了说明起见,这里有两个使用拟议语法创建的不同接口的示例。
interface CountFishInterface:
"Fish counting interface"
def oneFish():
"Increments the fish count by one"
def twoFish():
"Increments the fish count by two"
def getFishCount():
"Returns the fish count"
interface ColorFishInterface:
"Fish coloring interface"
def redFish():
"Sets the current fish color to red"
def blueFish():
"Sets the current fish color to blue"
def getFishColor():
"This returns the current fish color"
这段代码在执行时将创建两个名为 CountFishInterface 和 ColorFishInterface 的接口。这些接口由 interface 语句定义。
接口及其方法的散文文档来自文档字符串。方法签名信息来自 def 语句的签名。请注意,def 语句没有主体。接口不为任何事物实现服务;它只是描述一个。接口和接口方法上的文档字符串是强制性的,不能提供“pass”语句。pass 语句的接口等价物是空文档字符串。
您还可以创建“扩展”其他接口的接口。在这里,您可以看到一种新的接口类型,它扩展了 CountFishInterface 和 ColorFishInterface。
interface FishMarketInterface(CountFishInterface, ColorFishInterface):
"This is the documentation for the FishMarketInterface"
def getFishMonger():
"Returns the fish monger you can interact with"
def hireNewFishMonger(name):
"Hire a new fish monger"
def buySomeFish(quantity=1):
"Buy some fish at the market"
FishMarketInterface 扩展了 CountFishInterface 和 ColorfishInterface。
接口断言
下一步是通过创建一个断言它实现接口的具体 Python 类来将类和接口结合起来。这是一个可能的 FishMarket 组件示例。
class FishError(Error):
pass
class FishMarket implements FishMarketInterface:
number = 0
color = None
monger_name = 'Crusty Barnacles'
def __init__(self, number, color):
self.number = number
self.color = color
def oneFish(self):
self.number += 1
def twoFish(self):
self.number += 2
def redFish(self):
self.color = 'red'
def blueFish(self):
self.color = 'blue'
def getFishCount(self):
return self.number
def getFishColor(self):
return self.color
def getFishMonger(self):
return self.monger_name
def hireNewFishMonger(self, name):
self.monger_name = name
def buySomeFish(self, quantity=1):
if quantity > self.count:
raise FishError("There's not enough fish")
self.count -= quantity
return quantity
这个新类 FishMarket 定义了一个实现 FishMarketInterface 的具体类。implements 语句后面的对象称为“接口断言”。接口断言可以是接口对象,也可以是接口断言的元组。
像这样在 class 语句中提供的接口断言存储在类的 __implements__ 类属性中。在解释上述示例之后,您将有一个类语句,可以使用内置函数 'implements' 这样检查。
>>> FishMarket
<class FishMarket at 8140f50>
>>> FishMarket.__implements__
(<Interface FishMarketInterface at 81006f0>,)
>>> f = FishMarket(6, 'red')
>>> implements(f, FishMarketInterface)
1
>>>
一个类可以实现多个接口。例如,假设您有一个名为 ItemInterface 的接口,它描述了一个对象如何在容器对象中作为项目工作。如果您想断言 FishMarket 实例实现了 ItemInterface 接口以及 FishMarketInterface,您可以为 FishMarket 类提供一个包含接口对象元组的接口断言。
class FishMarket implements FishMarketInterface, ItemInterface:
# ...
如果您想断言一个类实现了一个接口,以及另一个类实现的所有接口,也可以使用接口断言。
class MyFishMarket implements FishMarketInterface, ItemInterface:
# ...
class YourFishMarket implements FooInterface, MyFishMarket.__implements__:
# ...
这个新类 YourFishMarket 断言它实现了 FooInterface,以及 MyFishMarket 类实现的接口。
值得详细介绍一下接口断言。接口断言要么是一个接口对象,要么是一个接口断言的元组。例如,
FooInterface
FooInterface, (BarInterface, BobInterface)
FooInterface, (BarInterface, (BobInterface, MyClass.__implements__))
都是有效的接口断言。当两个接口定义相同的属性时,断言中信息优先的顺序是从上到下,从左到右。
还有其他接口提案,为了简单起见,它们将类和接口的概念结合起来,以提供简单的接口强制执行。接口对象有一个 deferred 方法,它返回一个实现此行为的延迟类。
>>> FM = FishMarketInterface.deferred()
>>> class MyFM(FM): pass
>>> f = MyFM()
>>> f.getFishMonger()
Traceback (innermost last):
File "<stdin>", line 1, in ?
Interface.Exceptions.BrokenImplementation:
An object has failed to implement interface FishMarketInterface
The getFishMonger attribute was not provided.
>>>
这通过告知您忘记实现该接口的哪些内容来提供一些被动的接口强制执行。
正式接口语法
Python 语法定义在 Python 参考手册 [4] 中描述的修改后的 BNF 语法表示法中。本节使用此语法描述拟议的接口语法。
interfacedef: "interface" interfacename [extends] ":" suite
extends: "(" [expression_list] ")"
interfacename: identifier
接口定义是一个可执行语句。它首先评估 extends 列表(如果存在)。extends 列表中的每个项都应该评估为一个接口对象。
然后,接口的套件在一个新的执行帧中执行(参见 Python 参考手册第 4.1 节),使用新创建的局部命名空间和原始全局命名空间。当接口的套件执行完成时,其执行帧将被丢弃,但其局部命名空间将作为接口元素保存。然后使用 extends 列表作为基接口和保存的接口元素创建一个接口对象。接口名称在原始局部命名空间中绑定到此接口对象。
本 PEP 还提出了对 Python 的“类”语句的扩展。
classdef: "class" classname [inheritance] [implements] ":" suite
implements: "implements" implist
implist: expression-list
classname,
inheritance,
suite,
expression-list: see the Python Reference Manual
在执行类的套件之前,如果存在,则评估“继承”和“实现”语句。“继承”行为保持不变,如语言参考第 7.6 节所述。
如果存在“实现”,则在继承之后进行评估。这必须评估为接口规范,即接口或接口规范的元组。如果存在有效的接口规范,则断言将作为元组分配给类对象的“__implements__”属性。
本 PEP 不建议对函数定义或赋值的语法进行任何更改。
类与接口
上面示例接口没有描述其方法的任何行为,它们只是描述了典型 FishMarket 对象将实现的接口。
您可能会注意到接口从其他接口扩展与类从其他类子类化之间的相似性。这是一个类似的概念。然而,重要的是要注意接口扩展接口,类子类化类。您不能扩展类或子类化接口。类和接口是分开的。
类的目的是共享对象如何工作的实现。接口的目的是记录如何使用对象,而不是对象是如何实现的。可能存在几个具有非常不同实现的类实现相同的接口。
也可能用许多类实现一个接口,这些类混合了接口的功能片段,或者相反,可能一个类实现许多接口。因此,接口和类不应混淆或混杂。
接口感知内置函数
鉴于接口对象,对 Python 内置函数列表的一个有用扩展将是 implements()。这个内置函数将期望两个参数,一个对象和一个接口,如果对象实现了接口,则返回真值,否则返回假值。例如:
>>> interface FooInterface: pass
>>> class Foo implements FooInterface: pass
>>> f = Foo()
>>> implements(f, FooInterface)
1
目前,此功能在参考实现中作为 Interface 包中的函数存在,需要“import Interface”才能使用。它作为内置函数的存在纯粹是为了方便,对于使用接口不是必需的,并且类似于类的 isinstance()。
向后兼容性
拟议的接口模型不会在 Python 中引入任何向后兼容性问题。然而,拟议的语法会。
任何使用 interface 作为标识符的现有代码都将中断。将 interface 定义为新关键字可能会引入其他类型的向后不兼容性。对 Python 语法的此扩展不会以任何向后不兼容的方式更改任何现有语法。
新的 from __future__ Python 语法 (PEP 236) 和新的警告框架 (PEP 230) 是解决此向后不兼容性的理想选择。现在要使用接口语法,开发人员可以使用以下语句:
from __future__ import interfaces
此外,任何使用关键字 interface 作为标识符的代码都将收到来自 Python 的警告。在适当的时间之后,接口语法将成为标准,上述 import 语句将不起作用,并且任何名为 interface 的标识符都将引发异常。建议此时间段为 24 个月。
Python 拟议变更摘要
添加新的 interface 关键字并使用 implements 扩展类语法。
扩展类接口以包含 __implements__。
添加内置函数 'implements(obj, interface)'。
风险
本 PEP 提议向 Python 语言添加一个新关键字,interface。这将破坏现有代码。
未解决的问题
目标
语法
架构
异议
本 PEP 尚未在 python-dev 上讨论。
参考资料
版权
本文档已置于公共领域。
来源:https://github.com/python/peps/blob/main/peps/pep-0245.rst