527 lines
18 KiB
ReStructuredText
527 lines
18 KiB
ReStructuredText
PEP: 245
|
|
Title: Python Interface Syntax
|
|
Version: $Revision$
|
|
Last-Modified: $Date$
|
|
Author: Michel Pelletier <michel@users.sourceforge.net>
|
|
Status: Rejected
|
|
Type: Standards Track
|
|
Content-Type: text/x-rst
|
|
Created: 11-Jan-2001
|
|
Python-Version: 2.2
|
|
Post-History: 21-Mar-2001
|
|
|
|
|
|
.. note::
|
|
|
|
The no-longer-available Zope interfaces wiki page
|
|
(``https://www.zope.org/Wikis/Interfaces``) originally linked here,
|
|
containing links to further resources for this PEP,
|
|
can be `found on the Wayback Machine archive
|
|
<https://web.archive.org/web/20050327013919/http://www.zope.org/Wikis/Interfaces/FrontPage>`__.
|
|
Also, the Interface-Dev Zope mailing list on which this PEP was discussed
|
|
was shut down, but `its archives remain available
|
|
<https://mail.zope.dev/pipermail/interface-dev/>`__.
|
|
|
|
|
|
Rejection Notice
|
|
================
|
|
|
|
I'm rejecting this PEP. It's been five years now. While at some
|
|
point I expect that Python will have interfaces, it would be naive
|
|
to expect it to resemble the syntax in this PEP. Also, :pep:`246` is
|
|
being rejected in favor of something completely different; interfaces
|
|
won't play a role in adaptation or whatever will replace it. GvR.
|
|
|
|
|
|
Introduction
|
|
============
|
|
|
|
This PEP describes a proposed syntax for creating interface
|
|
objects in Python.
|
|
|
|
|
|
Overview
|
|
========
|
|
|
|
In addition to thinking about adding a static type system to
|
|
Python, the Types-SIG was also charged to devise an interface
|
|
system for Python. In December of 1998, Jim Fulton released a
|
|
prototype interfaces system based on discussions from the SIG.
|
|
Many of the issues and background information on this discussion
|
|
and prototype can be found in the SIG archives [1]_.
|
|
|
|
Around the end of 2000, Digital Creations began thinking about
|
|
better component model designs for Zope [2]_. Zope's future
|
|
component model relies heavily on interface objects. This led to
|
|
further development of Jim's "Scarecrow" interfaces prototype.
|
|
Starting with version 2.3, Zope comes with an Interface package as
|
|
standard software. Zope's Interface package is used as the
|
|
reference implementation for this PEP.
|
|
|
|
The syntax proposed by this PEP relies on syntax enhancements
|
|
describe in :pep:`232` and describes an underlying framework
|
|
which :pep:`233` could be based upon. There is some work being
|
|
done with regard to interface objects and Proxy objects, so for
|
|
those optional parts of this PEP you may want to see [3]_.
|
|
|
|
|
|
The Problem
|
|
===========
|
|
|
|
Interfaces are important because they solve a number of problems
|
|
that arise while developing software:
|
|
|
|
- There are many implied interfaces in Python, commonly referred
|
|
to as "protocols". Currently determining those protocols is
|
|
based on implementation introspection, but often that also
|
|
fails. For example, defining ``__getitem__`` implies both a
|
|
sequence and a mapping (the former with sequential, integer
|
|
keys). There is no way for the developer to be explicit about
|
|
which protocols the object intends to implement.
|
|
|
|
- Python is limited, from the developer's point of view, by the
|
|
split between types and classes. When types are expected, the
|
|
consumer uses code like 'type(foo) == type("")' to determine if
|
|
'foo' is a string. When instances of classes are expected, the
|
|
consumer uses 'isinstance(foo, MyString)' to determine if 'foo'
|
|
is an instance of the 'MyString' class. There is no unified
|
|
model for determining if an object can be used in a certain,
|
|
valid way.
|
|
|
|
- Python's dynamic typing is very flexible and powerful, but it
|
|
does not have the advantage of static typed languages that
|
|
provide type checking. Static typed languages provide you with
|
|
much more type safety, but are often overly verbose because
|
|
objects can only be generalized by common subclassing and used
|
|
specifically with casting (for example, in Java).
|
|
|
|
There are also a number of documentation problems that interfaces
|
|
try to solve.
|
|
|
|
- Developers waste a lot of time looking at the source code of
|
|
your system to figure out how objects work.
|
|
|
|
- Developers who are new to your system may misunderstand how your
|
|
objects work, causing, and possibly propagating, usage errors.
|
|
|
|
- Because a lack of interfaces means usage is inferred from the
|
|
source, developers may end up using methods and attributes that
|
|
are meant for "internal use only".
|
|
|
|
- Code inspection can be hard, and very discouraging to novice
|
|
programmers trying to properly understand code written by gurus.
|
|
|
|
- A lot of time is wasted when many people try very hard to
|
|
understand obscurity (like undocumented software). Effort spend
|
|
up front documenting interfaces will save much of this time in
|
|
the end.
|
|
|
|
Interfaces try to solve these problems by providing a way for you
|
|
to specify a contractual obligation for your object, documentation
|
|
on how to use an object, and a built-in mechanism for discovering
|
|
the contract and the documentation.
|
|
|
|
Python has very useful introspection features. It is well known
|
|
that this makes exploring concepts in the interactive interpreter
|
|
easier, because Python gives you the ability to look at all kinds
|
|
of information about the objects: the type, doc strings, instance
|
|
dictionaries, base classes, unbound methods and more.
|
|
|
|
Many of these features are oriented toward introspecting, using
|
|
and changing the implementation of software, and one of them ("doc
|
|
strings") is oriented toward providing documentation. This
|
|
proposal describes an extension to this natural introspection
|
|
framework that describes an object's interface.
|
|
|
|
|
|
Overview of the Interface Syntax
|
|
================================
|
|
|
|
For the most part, the syntax of interfaces is very much like the
|
|
syntax of classes, but future needs, or needs brought up in
|
|
discussion, may define new possibilities for interface syntax.
|
|
|
|
A formal BNF description of the syntax is givena later in the PEP,
|
|
for the purposes of illustration, here is an example of two
|
|
different interfaces created with the proposed syntax::
|
|
|
|
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"
|
|
|
|
This code, when evaluated, will create two interfaces called
|
|
``CountFishInterface`` and ``ColorFishInterface``. These interfaces
|
|
are defined by the ``interface`` statement.
|
|
|
|
The prose documentation for the interfaces and their methods come
|
|
from doc strings. The method signature information comes from the
|
|
signatures of the ``def`` statements. Notice how there is no body
|
|
for the def statements. The interface does not implement a
|
|
service to anything; it merely describes one. Documentation
|
|
strings on interfaces and interface methods are mandatory, a
|
|
'pass' statement cannot be provided. The interface equivalent of
|
|
a pass statement is an empty doc string.
|
|
|
|
You can also create interfaces that "extend" other interfaces.
|
|
Here, you can see a new type of Interface that extends the
|
|
CountFishInterface and 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"
|
|
|
|
The FishMarketInterface extends upon the CountFishInterface and
|
|
ColorfishInterface.
|
|
|
|
|
|
Interface Assertion
|
|
===================
|
|
|
|
The next step is to put classes and interfaces together by
|
|
creating a concrete Python class that asserts that it implements
|
|
an interface. Here is an example FishMarket component that might
|
|
do this::
|
|
|
|
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
|
|
|
|
This new class, FishMarket defines a concrete class which
|
|
implements the FishMarketInterface. The object following the
|
|
``implements`` statement is called an "interface assertion". An
|
|
interface assertion can be either an interface object, or tuple of
|
|
interface assertions.
|
|
|
|
The interface assertion provided in a ``class`` statement like this
|
|
is stored in the class's ``__implements__`` class attribute. After
|
|
interpreting the above example, you would have a class statement
|
|
that can be examined like this with an 'implements' built-in
|
|
function::
|
|
|
|
>>> FishMarket
|
|
<class FishMarket at 8140f50>
|
|
>>> FishMarket.__implements__
|
|
(<Interface FishMarketInterface at 81006f0>,)
|
|
>>> f = FishMarket(6, 'red')
|
|
>>> implements(f, FishMarketInterface)
|
|
1
|
|
>>>
|
|
|
|
A class can realize more than one interface. For example, say you
|
|
had an interface called ``ItemInterface`` that described how an
|
|
object worked as an item in a container object. If you wanted to
|
|
assert that FishMarket instances realized the ItemInterface
|
|
interface as well as the FishMarketInterface, you can provide an
|
|
interface assertion that contained a tuple of interface objects to
|
|
the FishMarket class::
|
|
|
|
class FishMarket implements FishMarketInterface, ItemInterface:
|
|
# ...
|
|
|
|
Interface assertions can also be used if you want to assert that
|
|
one class implements an interface, and all of the interfaces that
|
|
another class implements::
|
|
|
|
class MyFishMarket implements FishMarketInterface, ItemInterface:
|
|
# ...
|
|
|
|
class YourFishMarket implements FooInterface, MyFishMarket.__implements__:
|
|
# ...
|
|
|
|
This new class YourFishMarket, asserts that it implements the
|
|
FooInterface, as well as the interfaces implemented by the
|
|
MyFishMarket class.
|
|
|
|
It's worth going into a little bit more detail about interface
|
|
assertions. An interface assertion is either an interface object,
|
|
or a tuple of interface assertions. For example::
|
|
|
|
FooInterface
|
|
|
|
FooInterface, (BarInterface, BobInterface)
|
|
|
|
FooInterface, (BarInterface, (BobInterface, MyClass.__implements__))
|
|
|
|
Are all valid interface assertions. When two interfaces define
|
|
the same attributes, the order in which information is preferred
|
|
in the assertion is from top-to-bottom, left-to-right.
|
|
|
|
There are other interface proposals that, in the need for
|
|
simplicity, have combined the notion of class and interface to
|
|
provide simple interface enforcement. Interface objects have a
|
|
``deferred`` method that returns a deferred class that implements
|
|
this behavior::
|
|
|
|
>>> 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.
|
|
>>>
|
|
|
|
This provides for a bit of passive interface enforcement by
|
|
telling you what you forgot to do to implement that interface.
|
|
|
|
|
|
Formal Interface Syntax
|
|
=======================
|
|
|
|
Python syntax is defined in a modified BNF grammar notation
|
|
described in the Python Reference Manual [4]_. This section
|
|
describes the proposed interface syntax using this grammar::
|
|
|
|
interfacedef: "interface" interfacename [extends] ":" suite
|
|
extends: "(" [expression_list] ")"
|
|
interfacename: identifier
|
|
|
|
An interface definition is an executable statement. It first
|
|
evaluates the extends list, if present. Each item in the extends
|
|
list should evaluate to an interface object.
|
|
|
|
The interface's suite is then executed in a new execution frame
|
|
(see the Python Reference Manual, section 4.1), using a newly
|
|
created local namespace and the original global namespace. When
|
|
the interface's suite finishes execution, its execution frame is
|
|
discarded but its local namespace is saved as interface elements.
|
|
An interface object is then created using the extends list for the
|
|
base interfaces and the saved interface elements. The interface
|
|
name is bound to this interface object in the original local
|
|
namespace.
|
|
|
|
This PEP also proposes an extension to Python's 'class' statement::
|
|
|
|
classdef: "class" classname [inheritance] [implements] ":" suite
|
|
implements: "implements" implist
|
|
implist: expression-list
|
|
|
|
classname,
|
|
inheritance,
|
|
suite,
|
|
expression-list: see the Python Reference Manual
|
|
|
|
Before a class' suite is executed, the 'inheritance' and
|
|
'implements' statements are evaluated, if present. The
|
|
'inheritance' behavior is unchanged as defined in Section 7.6 of
|
|
the Language Reference.
|
|
|
|
The 'implements', if present, is evaluated after inheritance.
|
|
This must evaluate to an interface specification, which is either
|
|
an interface, or a tuple of interface specifications. If a valid
|
|
interface specification is present, the assertion is assigned to
|
|
the class object's '__implements__' attribute, as a tuple.
|
|
|
|
This PEP does not propose any changes to the syntax of function
|
|
definitions or assignments.
|
|
|
|
|
|
Classes and Interfaces
|
|
======================
|
|
|
|
The example interfaces above do not describe any kind of behavior
|
|
for their methods, they just describe an interface that a typical
|
|
FishMarket object would realize.
|
|
|
|
You may notice a similarity between interfaces extending from
|
|
other interfaces and classes sub-classing from other classes.
|
|
This is a similar concept. However it is important to note that
|
|
interfaces extend interfaces and classes subclass classes. You
|
|
cannot extend a class or subclass an interface. Classes and
|
|
interfaces are separate.
|
|
|
|
The purpose of a class is to share the implementation of how an
|
|
object works. The purpose of an interface is to document how to
|
|
work with an object, not how the object is implemented. It is
|
|
possible to have several different classes with very different
|
|
implementations realize the same interface.
|
|
|
|
It's also possible to implement one interface with many classes
|
|
that mix in pieces the functionality of the interface or,
|
|
conversely, it's possible to have one class implement many
|
|
interfaces. Because of this, interfaces and classes should not be
|
|
confused or intermingled.
|
|
|
|
|
|
Interface-aware built-ins
|
|
=========================
|
|
|
|
A useful extension to Python's list of built-in functions in the
|
|
light of interface objects would be ``implements()``. This builtin
|
|
would expect two arguments, an object and an interface, and return
|
|
a true value if the object implements the interface, false
|
|
otherwise. For example::
|
|
|
|
>>> interface FooInterface: pass
|
|
>>> class Foo implements FooInterface: pass
|
|
>>> f = Foo()
|
|
>>> implements(f, FooInterface)
|
|
1
|
|
|
|
Currently, this functionality exists in the reference
|
|
implementation as functions in the ``Interface`` package, requiring
|
|
an "import Interface" to use it. Its existence as a built-in
|
|
would be purely for a convenience, and not necessary for using
|
|
interfaces, and analogous to ``isinstance()`` for classes.
|
|
|
|
|
|
Backward Compatibility
|
|
======================
|
|
|
|
The proposed interface model does not introduce any backward
|
|
compatibility issues in Python. The proposed syntax, however,
|
|
does.
|
|
|
|
Any existing code that uses ``interface`` as an identifier will
|
|
break. There may be other kinds of backwards incompatibility that
|
|
defining ``interface`` as a new keyword will introduce. This
|
|
extension to Python's syntax does not change any existing syntax
|
|
in any backward incompatible way.
|
|
|
|
The new ``from __future__`` Python syntax (:pep:`236`), and the new warning
|
|
framework (:pep:`230`) is ideal for resolving this backward
|
|
incompatibility. To use interface syntax now, a developer could
|
|
use the statement::
|
|
|
|
from __future__ import interfaces
|
|
|
|
In addition, any code that uses the keyword ``interface`` as an
|
|
identifier will be issued a warning from Python. After the
|
|
appropriate period of time, the interface syntax would become
|
|
standard, the above import statement would do nothing, and any
|
|
identifiers named ``interface`` would raise an exception. This
|
|
period of time is proposed to be 24 months.
|
|
|
|
|
|
Summary of Proposed Changes to Python
|
|
=====================================
|
|
|
|
Adding new ``interface`` keyword and extending class syntax with
|
|
``implements``.
|
|
|
|
Extending class interface to include ``__implements__``.
|
|
|
|
Add 'implements(obj, interface)' built-in.
|
|
|
|
|
|
Risks
|
|
=====
|
|
|
|
This PEP proposes adding one new keyword to the Python language,
|
|
``interface``. This will break code.
|
|
|
|
|
|
Open Issues
|
|
===========
|
|
|
|
Goals
|
|
-----
|
|
|
|
Syntax
|
|
------
|
|
|
|
Architecture
|
|
------------
|
|
|
|
|
|
Dissenting Opinion
|
|
==================
|
|
|
|
This PEP has not yet been discussed on python-dev.
|
|
|
|
|
|
References
|
|
==========
|
|
|
|
.. [1] https://mail.python.org/pipermail/types-sig/1998-December/date.html
|
|
|
|
.. [2] http://www.zope.org
|
|
|
|
.. [3] http://www.lemburg.com/files/python/mxProxy.html
|
|
|
|
.. [4] Python Reference Manual
|
|
http://docs.python.org/reference/
|
|
|
|
|
|
Copyright
|
|
=========
|
|
|
|
This document has been placed in the public domain.
|
|
|
|
|
|
..
|
|
Local Variables:
|
|
mode: indented-text
|
|
indent-tabs-mode: nil
|
|
End:
|