496 lines
18 KiB
Plaintext
496 lines
18 KiB
Plaintext
PEP: 245
|
||
Title: Python Interface Syntax
|
||
Version: $Revision$
|
||
Author: Michel Pelletier <michel@users.sourceforge.net>
|
||
Discussions-To: http://www.zope.org/Wikis/Interfaces
|
||
Status: Draft
|
||
Type: Standards Track
|
||
Created: 11-Jan-2001
|
||
Python-Version: 2.2
|
||
Post-History: 21-Mar-2001
|
||
|
||
|
||
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 [3] and describes an underlying framework
|
||
which PEP 233 [4] 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[5].
|
||
|
||
|
||
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 explict 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 langauges provide you with
|
||
much more type saftey, but are often overly verbose because
|
||
objects can only be generalized by common subclassing and used
|
||
specificly 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 FishMarketInteface 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, (BarInteface, 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 grammer notation
|
||
described in the Python Reference Manual [8]. 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[6], and the new warning
|
||
framework [7] 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] http://mail.python.org/pipermail/types-sig/1998-December/date.html
|
||
|
||
[2] http://www.zope.org
|
||
|
||
[3] PEP 232, Function Attributes, Warsaw
|
||
http://www.python.org/peps/pep-0232.html
|
||
|
||
[4] PEP 233, Python Online Help, Prescod
|
||
http://www.python.org/peps/pep-0233.html
|
||
|
||
[5] http://www.lemburg.com/files/python/mxProxy.html
|
||
|
||
[6] PEP 236, Back to the __future__, Peters
|
||
http://www.python.org/peps/pep-0236.html
|
||
|
||
[7] PEP 230, Warning Framework, van Rossum
|
||
http://www.python.org/peps/pep-0236.html
|
||
|
||
|
||
Copyright
|
||
|
||
This document has been placed in the public domain.
|
||
|
||
|
||
|
||
Local Variables:
|
||
mode: indented-text
|
||
indent-tabs-mode: nil
|
||
End:
|
||
|