reSTify PEP 246 (#409)

This commit is contained in:
Huang Huang 2017-09-13 07:22:04 +08:00 committed by Guido van Rossum
parent cd6642d2f2
commit c510df9ac8
1 changed files with 450 additions and 431 deletions

View File

@ -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