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 的“Scarecrow”接口原型的进一步发展。从 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 使您能够查看有关对象的各种信息:类型、doc 字符串、实例字典、基类、未绑定方法等等。
许多这些功能都面向内省、使用和更改软件的实现,其中一个(“doc 字符串”)面向提供文档。此提案描述了此自然内省框架的扩展,该扩展描述了对象的接口。
接口语法的概述
在很大程度上,接口的语法非常类似于类的语法,但未来的需求或讨论中提出的需求可能会为接口语法定义新的可能性。
后面将给出语法的正式 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
语句定义。
接口及其方法的散文文档来自 doc 字符串。方法签名信息来自 def
语句的签名。请注意,def 语句没有主体。接口不会对任何内容实施服务;它仅仅描述一个服务。接口和接口方法的文档字符串是强制性的,不能提供“pass”语句。接口等效于 pass 语句是一个空 doc 字符串。
您还可以创建“扩展”其他接口的接口。在这里,您可以看到一种新的接口类型,它扩展了 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 参考手册中描述的修改后的 BNF 语法表示法中定义的 [4]。本节使用此语法描述拟议的接口语法。
interfacedef: "interface" interfacename [extends] ":" suite
extends: "(" [expression_list] ")"
interfacename: identifier
接口定义是可执行语句。首先评估扩展列表(如果存在)。扩展列表中的每个项目都应评估为接口对象。
然后在新的执行框架中执行接口的套件(参见 Python 参考手册,第 4.1 节),使用新创建的本地命名空间和原始全局命名空间。当接口的套件完成执行时,其执行框架将被丢弃,但其本地命名空间将作为接口元素保存。然后,使用扩展列表作为基接口和保存的接口元素创建接口对象。接口名称在原始本地命名空间中绑定到此接口对象。
此 PEP 还建议扩展 Python 的“class”语句。
classdef: "class" classname [inheritance] [implements] ":" suite
implements: "implements" implist
implist: expression-list
classname,
inheritance,
suite,
expression-list: see the Python Reference Manual
在执行类的套件之前,将评估“inheritance”和“implements”语句(如果存在)。“inheritance”行为与语言参考中第 7.6 节中定义的行为相同。
如果存在“implements”,则在继承之后进行评估。这必须评估为接口规范,该规范可以是接口,也可以是接口规范元组。如果存在有效的接口规范,则断言将作为元组分配给类对象的“__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 的警告。 在适当的时间段后,接口语法将成为标准,上面的导入语句将不再起作用,任何名为 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
最后修改时间:2023-09-09 17:39:29 GMT