reSTify PEP 246 (#409)
This commit is contained in:
parent
cd6642d2f2
commit
c510df9ac8
151
pep-0246.txt
151
pep-0246.txt
|
@ -6,12 +6,14 @@ Author: aleaxit@gmail.com (Alex Martelli),
|
|||
cce@clarkevans.com (Clark C. Evans)
|
||||
Status: Rejected
|
||||
Type: Standards Track
|
||||
Content-Type: text/x-rst
|
||||
Created: 21-Mar-2001
|
||||
Python-Version: 2.5
|
||||
Post-History: 29-Mar-2001, 10-Jan-2005
|
||||
|
||||
|
||||
Rejection Notice
|
||||
================
|
||||
|
||||
I'm rejecting this PEP. Something much better is about to happen;
|
||||
it's too early to say exactly what, but it's not going to resemble
|
||||
|
@ -20,6 +22,7 @@ Rejection Notice
|
|||
|
||||
|
||||
Abstract
|
||||
========
|
||||
|
||||
This proposal puts forth an extensible cooperative mechanism for
|
||||
the adaptation of an incoming object to a context which expects an
|
||||
|
@ -52,29 +55,30 @@ Abstract
|
|||
"copy_reg" module offers for serialization and persistence.
|
||||
|
||||
This proposal does not specifically constrain what a protocol
|
||||
_is_, what "compliance to a protocol" exactly _means_, nor what
|
||||
*is*, what "compliance to a protocol" exactly *means*, nor what
|
||||
precisely a wrapper is supposed to do. These omissions are
|
||||
intended to leave this proposal compatible with both existing
|
||||
categories of protocols, such as the existing system of type and
|
||||
classes, as well as the many concepts for "interfaces" as such
|
||||
which have been proposed or implemented for Python, such as the
|
||||
one in PEP 245 [1], the one in Zope3 [2], or the ones discussed in
|
||||
the BDFL's Artima blog in late 2004 and early 2005 [3]. However,
|
||||
one in PEP 245 [1]_, the one in Zope3 [2]_, or the ones discussed in
|
||||
the BDFL's Artima blog in late 2004 and early 2005 [3]_. However,
|
||||
some reflections on these subjects, intended to be suggestive and
|
||||
not normative, are also included.
|
||||
|
||||
|
||||
Motivation
|
||||
==========
|
||||
|
||||
Currently there is no standardized mechanism in Python for
|
||||
checking if an object supports a particular protocol. Typically,
|
||||
existence of certain methods, particularly special methods such as
|
||||
__getitem__, is used as an indicator of support for a particular
|
||||
``__getitem__``, is used as an indicator of support for a particular
|
||||
protocol. This technique works well for a few specific protocols
|
||||
blessed by the BDFL (Benevolent Dictator for Life). The same can
|
||||
be said for the alternative technique based on checking
|
||||
'isinstance' (the built-in class "basestring" exists specifically
|
||||
to let you use 'isinstance' to check if an object "is a [built-in]
|
||||
to let you use 'isinstance' to check if an object "is a \[built-in\]
|
||||
string"). Neither approach is easily and generally extensible to
|
||||
other protocols, defined by applications and third party
|
||||
frameworks, outside of the standard Python core.
|
||||
|
@ -110,6 +114,7 @@ Motivation
|
|||
|
||||
|
||||
Requirements
|
||||
============
|
||||
|
||||
When considering an object's compliance with a protocol, there are
|
||||
several cases to be examined:
|
||||
|
@ -141,36 +146,37 @@ Requirements
|
|||
arguments types), or extends the co-domain to include return
|
||||
values which the base class may never produce ("contra-variance"
|
||||
on return types). While compliance based on class inheritance
|
||||
_should_ be automatic, this proposal allows an object to signal
|
||||
*should* be automatic, this proposal allows an object to signal
|
||||
that it is not compliant with a base class protocol.
|
||||
|
||||
If Python gains some standard "official" mechanism for interfaces,
|
||||
however, then the "fast-path" case (a) can and should be extended
|
||||
to the protocol being an interface, and the object an instance of
|
||||
a type or class claiming compliance with that interface. For
|
||||
example, if the "interface" keyword discussed in [3] is adopted
|
||||
example, if the "interface" keyword discussed in [3]_ is adopted
|
||||
into Python, the "fast path" of case (a) could be used, since
|
||||
instantiable classes implementing an interface would not be
|
||||
allowed to break substitutability.
|
||||
|
||||
|
||||
Specification
|
||||
=============
|
||||
|
||||
This proposal introduces a new built-in function, adapt(), which
|
||||
This proposal introduces a new built-in function, ``adapt()``, which
|
||||
is the basis for supporting these requirements.
|
||||
|
||||
The adapt() function has three parameters:
|
||||
The ``adapt()`` function has three parameters:
|
||||
|
||||
- `obj', the object to be adapted
|
||||
- ``obj``, the object to be adapted
|
||||
|
||||
- `protocol', the protocol requested of the object
|
||||
- ``protocol``, the protocol requested of the object
|
||||
|
||||
- `alternate', an optional object to return if the object could
|
||||
- ``alternate``, an optional object to return if the object could
|
||||
not be adapted
|
||||
|
||||
A successful result of the adapt() function returns either the
|
||||
object passed `obj', if the object is already compliant with the
|
||||
protocol, or a secondary object `wrapper', which provides a view
|
||||
A successful result of the ``adapt()`` function returns either the
|
||||
object passed ``obj``, if the object is already compliant with the
|
||||
protocol, or a secondary object ``wrapper``, which provides a view
|
||||
of the object compliant with the protocol. The definition of
|
||||
wrapper is deliberately vague, and a wrapper is allowed to be a
|
||||
full object with its own state if necessary. However, the design
|
||||
|
@ -181,76 +187,76 @@ Specification
|
|||
An excellent example of adaptation wrapper is an instance of
|
||||
StringIO which adapts an incoming string to be read as if it was a
|
||||
textfile: the wrapper holds a reference to the string, but deals
|
||||
by itself with the "current point of reading" (from _where_ in the
|
||||
by itself with the "current point of reading" (from *where* in the
|
||||
wrapped strings will the characters for the next, e.g., "readline"
|
||||
call come from), because it cannot delegate it to the wrapped
|
||||
object (a string has no concept of "current point of reading" nor
|
||||
anything else even remotely related to that concept).
|
||||
|
||||
A failure to adapt the object to the protocol raises an
|
||||
AdaptationError (which is a subclass of TypeError), unless the
|
||||
``AdaptationError`` (which is a subclass of ``TypeError``), unless the
|
||||
alternate parameter is used, in this case the alternate argument
|
||||
is returned instead.
|
||||
|
||||
To enable the first case listed in the requirements, the adapt()
|
||||
To enable the first case listed in the requirements, the ``adapt()``
|
||||
function first checks to see if the object's type or the object's
|
||||
class are identical to the protocol. If so, then the adapt()
|
||||
class are identical to the protocol. If so, then the ``adapt()``
|
||||
function returns the object directly without further ado.
|
||||
|
||||
To enable the second case, when the object knows about the
|
||||
protocol, the object must have a __conform__() method. This
|
||||
protocol, the object must have a ``__conform__()`` method. This
|
||||
optional method takes two arguments:
|
||||
|
||||
- `self', the object being adapted
|
||||
- ``self``, the object being adapted
|
||||
|
||||
- `protocol, the protocol requested
|
||||
- ``protocol``, the protocol requested
|
||||
|
||||
Just like any other special method in today's Python, __conform__
|
||||
Just like any other special method in today's Python, ``__conform__``
|
||||
is meant to be taken from the object's class, not from the object
|
||||
itself (for all objects, except instances of "classic classes" as
|
||||
long as we must still support the latter). This enables a
|
||||
possible 'tp_conform' slot to be added to Python's type objects in
|
||||
the future, if desired.
|
||||
|
||||
The object may return itself as the result of __conform__ to
|
||||
The object may return itself as the result of ``__conform__`` to
|
||||
indicate compliance. Alternatively, the object also has the
|
||||
option of returning a wrapper object compliant with the protocol.
|
||||
If the object knows it is not compliant although it belongs to a
|
||||
type which is a subclass of the protocol, then __conform__ should
|
||||
raise a LiskovViolation exception (a subclass of AdaptationError).
|
||||
type which is a subclass of the protocol, then ``__conform__`` should
|
||||
raise a ``LiskovViolation`` exception (a subclass of ``AdaptationError``).
|
||||
Finally, if the object cannot determine its compliance, it should
|
||||
return None to enable the remaining mechanisms. If __conform__
|
||||
return ``None`` to enable the remaining mechanisms. If ``__conform__``
|
||||
raises any other exception, "adapt" just propagates it.
|
||||
|
||||
To enable the third case, when the protocol knows about the
|
||||
object, the protocol must have an __adapt__() method. This
|
||||
object, the protocol must have an ``__adapt__()`` method. This
|
||||
optional method takes two arguments:
|
||||
|
||||
- `self', the protocol requested
|
||||
- ``self``, the protocol requested
|
||||
|
||||
- `obj', the object being adapted
|
||||
- ``obj``, the object being adapted
|
||||
|
||||
If the protocol finds the object to be compliant, it can return
|
||||
obj directly. Alternatively, the method may return a wrapper
|
||||
compliant with the protocol. If the protocol knows the object is
|
||||
not compliant although it belongs to a type which is a subclass of
|
||||
the protocol, then __adapt__ should raise a LiskovViolation
|
||||
exception (a subclass of AdaptationError). Finally, when
|
||||
the protocol, then ``__adapt__`` should raise a ``LiskovViolation``
|
||||
exception (a subclass of ``AdaptationError``). Finally, when
|
||||
compliance cannot be determined, this method should return None to
|
||||
enable the remaining mechanisms. If __adapt__ raises any other
|
||||
enable the remaining mechanisms. If ``__adapt__`` raises any other
|
||||
exception, "adapt" just propagates it.
|
||||
|
||||
The fourth case, when the object's class is a sub-class of the
|
||||
protocol, is handled by the built-in adapt() function. Under
|
||||
protocol, is handled by the built-in ``adapt()`` function. Under
|
||||
normal circumstances, if "isinstance(object, protocol)" then
|
||||
adapt() returns the object directly. However, if the object is
|
||||
not substitutable, either the __conform__() or __adapt__()
|
||||
methods, as above mentioned, may raise an LiskovViolation (a
|
||||
subclass of AdaptationError) to prevent this default behavior.
|
||||
``adapt()`` returns the object directly. However, if the object is
|
||||
not substitutable, either the ``__conform__()`` or ``__adapt__()``
|
||||
methods, as above mentioned, may raise an ``LiskovViolation`` (a
|
||||
subclass of ``AdaptationError``) to prevent this default behavior.
|
||||
|
||||
If none of the first four mechanisms worked, as a last-ditch
|
||||
attempt, 'adapt' falls back to checking a registry of adapter
|
||||
factories, indexed by the protocol and the type of `obj', to meet
|
||||
factories, indexed by the protocol and the type of ``obj``, to meet
|
||||
the fifth case. Adapter factories may be dynamically registered
|
||||
and removed from that registry to provide "third party adaptation"
|
||||
of objects and protocols that have no knowledge of each other, in
|
||||
|
@ -258,6 +264,7 @@ Specification
|
|||
|
||||
|
||||
Intended Use
|
||||
============
|
||||
|
||||
The typical intended use of adapt is in code which has received
|
||||
some object X "from the outside", either as an argument or as the
|
||||
|
@ -277,7 +284,7 @@ Intended Use
|
|||
[imagine a method claiming to be threadsafe but being in fact
|
||||
subject to some subtle race condition, for example]; lack of
|
||||
"pragmatic" compliance will generally lead to code that runs
|
||||
``correctly'', but too slowly for practical use, or sometimes to
|
||||
``correctly``, but too slowly for practical use, or sometimes to
|
||||
exhaustion of resources such as memory or disk space).
|
||||
|
||||
When protocol Y is a concrete type or class, compliance to it is
|
||||
|
@ -286,9 +293,9 @@ Intended Use
|
|||
and pragmatics. For example, a hypothetical object X that is a
|
||||
singly-linked list should not claim compliance with protocol
|
||||
'list', even if it implements all of list's methods: the fact that
|
||||
indexing X[n] takes time O(n), while the same operation would be
|
||||
indexing ``X[n]`` takes time O(n), while the same operation would be
|
||||
O(1) on a list, makes a difference. On the other hand, an
|
||||
instance of StringIO.StringIO does comply with protocol 'file',
|
||||
instance of ``StringIO.StringIO`` does comply with protocol 'file',
|
||||
even though some operations (such as those of module 'marshal')
|
||||
may not allow substituting one for the other because they perform
|
||||
explicit type-checks: such type-checks are "beyond the pale" from
|
||||
|
@ -321,29 +328,31 @@ Intended Use
|
|||
wrapper object Z, which holds a reference to X, and implements
|
||||
whatever operation Y requires, mostly by delegating to X in
|
||||
appropriate ways. For example, if X is a string and Y is 'file',
|
||||
the proper way to adapt X to Y is to make a StringIO(X), *NOT* to
|
||||
call file(X) [which would try to open a file named by X].
|
||||
the proper way to adapt X to Y is to make a ``StringIO(X)``, **NOT** to
|
||||
call ``file(X)`` [which would try to open a file named by X].
|
||||
|
||||
Numeric types and protocols may need to be an exception to this
|
||||
"adaptation is not casting" mantra, however.
|
||||
|
||||
|
||||
Guido's "Optional Static Typing: Stop the Flames" Blog Entry
|
||||
============================================================
|
||||
|
||||
A typical simple use case of adaptation would be:
|
||||
|
||||
A typical simple use case of adaptation would be::
|
||||
|
||||
def f(X):
|
||||
X = adapt(X, Y)
|
||||
# continue by using X according to protocol Y
|
||||
|
||||
In [4], the BDFL has proposed introducing the syntax:
|
||||
In [4]_, the BDFL has proposed introducing the syntax::
|
||||
|
||||
def f(X: Y):
|
||||
# continue by using X according to protocol Y
|
||||
|
||||
to be a handy shortcut for exactly this typical use of adapt, and,
|
||||
as a basis for experimentation until the parser has been modified
|
||||
to accept this new syntax, a semantically equivalent decorator:
|
||||
to accept this new syntax, a semantically equivalent decorator::
|
||||
|
||||
@arguments(Y)
|
||||
def f(X):
|
||||
|
@ -355,13 +364,16 @@ Guido's "Optional Static Typing: Stop the Flames" Blog Entry
|
|||
|
||||
|
||||
Reference Implementation and Test Cases
|
||||
=======================================
|
||||
|
||||
The following reference implementation does not deal with classic
|
||||
classes: it consider only new-style classes. If classic classes
|
||||
need to be supported, the additions should be pretty clear, though
|
||||
a bit messy (x.__class__ vs type(x), getting boundmethods directly
|
||||
a bit messy (``x.__class__`` vs ``type(x)``, getting boundmethods directly
|
||||
from the object rather than from the type, and so on).
|
||||
|
||||
::
|
||||
|
||||
-----------------------------------------------------------------
|
||||
adapt.py
|
||||
-----------------------------------------------------------------
|
||||
|
@ -531,6 +543,7 @@ Reference Implementation and Test Cases
|
|||
|
||||
|
||||
Relationship To Microsoft's QueryInterface
|
||||
==========================================
|
||||
|
||||
Although this proposal has some similarities to Microsoft's (COM)
|
||||
QueryInterface, it differs by a number of aspects.
|
||||
|
@ -551,7 +564,7 @@ Relationship To Microsoft's QueryInterface
|
|||
a kind of equivalence relation -- they must be reflexive,
|
||||
symmetrical, and transitive, in specific senses. The equivalent
|
||||
conditions for protocol adaptation according to this proposal
|
||||
would also represent desirable properties:
|
||||
would also represent desirable properties::
|
||||
|
||||
# given, to start with, a successful adaptation:
|
||||
X_as_Y = adapt(X, Y)
|
||||
|
@ -582,7 +595,7 @@ Relationship To Microsoft's QueryInterface
|
|||
The latter would not be controversial if we knew that inheritance
|
||||
always implies Liskov substitutability, which, unfortunately we
|
||||
don't. If some special form, such as the interfaces proposed in
|
||||
[4], could indeed ensure Liskov substitutability, then for that
|
||||
[4]_, could indeed ensure Liskov substitutability, then for that
|
||||
kind of inheritance, only, we could perhaps assert that if X
|
||||
conforms to Y and Y inherits from Z then X conforms to Z... but
|
||||
only if substitutability was taken in a very strong sense to
|
||||
|
@ -593,8 +606,8 @@ Relationship To Microsoft's QueryInterface
|
|||
detailed above.
|
||||
|
||||
Similarly, transitivity might imply multiple "internal" adaptation
|
||||
passes to get the result of adapt(X, Z) via some intermediate Y,
|
||||
intrinsically like adapt(adapt(X, Y), Z), for some suitable and
|
||||
passes to get the result of ``adapt(X, Z)`` via some intermediate Y,
|
||||
intrinsically like ``adapt(adapt(X, Y), Z)``, for some suitable and
|
||||
automatically chosen Y. Again, this may perhaps be feasible under
|
||||
suitably strong constraints, but the practical implications of
|
||||
such a scheme are still unclear to this proposal's authors. Thus,
|
||||
|
@ -604,15 +617,16 @@ Relationship To Microsoft's QueryInterface
|
|||
For an implementation of the original version of this proposal
|
||||
which performs more advanced processing in terms of transitivity,
|
||||
and of the effects of inheritance, see Phillip J. Eby's
|
||||
PyProtocols [5]. The documentation accompanying PyProtocols is
|
||||
``PyProtocols`` [5]_. The documentation accompanying ``PyProtocols`` is
|
||||
well worth studying for its considerations on how adapters should
|
||||
be coded and used, and on how adaptation can remove any need for
|
||||
typechecking in application code.
|
||||
|
||||
|
||||
Questions and Answers
|
||||
=====================
|
||||
|
||||
Q: What benefit does this proposal provide?
|
||||
* Q: What benefit does this proposal provide?
|
||||
|
||||
A: The typical Python programmer is an integrator, someone who is
|
||||
connecting components from various suppliers. Often, to
|
||||
|
@ -625,7 +639,7 @@ Questions and Answers
|
|||
and figuring out how to deploy the adapter takes time.
|
||||
|
||||
This technique enables suppliers to work with each other
|
||||
directly, by implementing __conform__ or __adapt__ as
|
||||
directly, by implementing ``__conform__`` or ``__adapt__`` as
|
||||
necessary. This frees the integrator from making their own
|
||||
adapters. In essence, this allows the components to have a
|
||||
simple dialogue among themselves. The integrator simply
|
||||
|
@ -654,7 +668,7 @@ Questions and Answers
|
|||
requiring SAX2 are unaware of each other.
|
||||
|
||||
|
||||
Q: Why does this have to be built-in, can't it be standalone?
|
||||
* Q: Why does this have to be built-in, can't it be standalone?
|
||||
|
||||
A: Yes, it does work standalone. However, if it is built-in, it
|
||||
has a greater chance of usage. The value of this proposal is
|
||||
|
@ -672,25 +686,27 @@ Questions and Answers
|
|||
and even be of some help to a type inference system.
|
||||
|
||||
|
||||
Q: Why the verbs __conform__ and __adapt__?
|
||||
* Q: Why the verbs ``__conform__`` and ``__adapt__``?
|
||||
|
||||
A: conform, verb intransitive
|
||||
|
||||
1. To correspond in form or character; be similar.
|
||||
2. To act or be in accord or agreement; comply.
|
||||
3. To act in accordance with current customs or modes.
|
||||
|
||||
adapt, verb transitive
|
||||
1. To make suitable to or fit for a specific use or
|
||||
situation.
|
||||
|
||||
1. To make suitable to or fit for a specific use or situation.
|
||||
|
||||
Source: The American Heritage Dictionary of the English
|
||||
Language, Third Edition
|
||||
|
||||
|
||||
Backwards Compatibility
|
||||
=======================
|
||||
|
||||
There should be no problem with backwards compatibility unless
|
||||
someone had used the special names __conform__ or __adapt__ in
|
||||
someone had used the special names ``__conform__`` or ``__adapt__`` in
|
||||
other ways, but this seems unlikely, and, in any case, user code
|
||||
should never use special names for non-standard purposes.
|
||||
|
||||
|
@ -699,6 +715,7 @@ Backwards Compatibility
|
|||
|
||||
|
||||
Credits
|
||||
=======
|
||||
|
||||
This proposal was created in large part by the feedback of the
|
||||
talented individuals on the main Python mailing lists and the
|
||||
|
@ -718,25 +735,27 @@ Credits
|
|||
|
||||
|
||||
References and Footnotes
|
||||
========================
|
||||
|
||||
[1] PEP 245, Python Interface Syntax, Pelletier
|
||||
.. [1] PEP 245, Python Interface Syntax, Pelletier
|
||||
http://www.python.org/dev/peps/pep-0245/
|
||||
|
||||
[2] http://www.zope.org/Wikis/Interfaces/FrontPage
|
||||
.. [2] http://www.zope.org/Wikis/Interfaces/FrontPage
|
||||
|
||||
[3] http://www.artima.com/weblogs/index.jsp?blogger=guido
|
||||
.. [3] http://www.artima.com/weblogs/index.jsp?blogger=guido
|
||||
|
||||
[4] http://www.artima.com/weblogs/viewpost.jsp?thread=87182
|
||||
.. [4] http://www.artima.com/weblogs/viewpost.jsp?thread=87182
|
||||
|
||||
[5] http://peak.telecommunity.com/PyProtocols.html
|
||||
.. [5] http://peak.telecommunity.com/PyProtocols.html
|
||||
|
||||
|
||||
Copyright
|
||||
=========
|
||||
|
||||
This document has been placed in the public domain.
|
||||
|
||||
|
||||
|
||||
..
|
||||
Local Variables:
|
||||
mode: indented-text
|
||||
indent-tabs-mode: nil
|
||||
|
|
Loading…
Reference in New Issue