2000-07-15 19:28:27 -04:00
|
|
|
PEP: 208
|
|
|
|
Title: Reworking the Coercion Model
|
2022-10-05 12:48:43 -04:00
|
|
|
Author: Neil Schemenauer <nas@arctrix.com>, Marc-André Lemburg <mal@lemburg.com>
|
2001-01-19 17:32:11 -05:00
|
|
|
Status: Final
|
2000-12-04 22:17:08 -05:00
|
|
|
Type: Standards Track
|
2017-02-02 12:58:49 -05:00
|
|
|
Content-Type: text/x-rst
|
2000-12-04 22:17:08 -05:00
|
|
|
Created: 04-Dec-2000
|
2000-07-15 19:28:27 -04:00
|
|
|
Python-Version: 2.1
|
2007-06-19 00:20:07 -04:00
|
|
|
Post-History:
|
2000-12-04 22:17:08 -05:00
|
|
|
|
|
|
|
|
|
|
|
Abstract
|
2017-02-02 12:58:49 -05:00
|
|
|
========
|
2000-12-04 22:17:08 -05:00
|
|
|
|
2017-02-02 12:58:49 -05:00
|
|
|
Many Python types implement numeric operations. When the arguments of
|
|
|
|
a numeric operation are of different types, the interpreter tries to
|
|
|
|
coerce the arguments into a common type. The numeric operation is
|
|
|
|
then performed using this common type. This PEP proposes a new type
|
|
|
|
flag to indicate that arguments to a type's numeric operations should
|
|
|
|
not be coerced. Operations that do not support the supplied types
|
|
|
|
indicate it by returning a new singleton object. Types which do not
|
|
|
|
set the type flag are handled in a backwards compatible manner.
|
|
|
|
Allowing operations handle different types is often simpler, more
|
|
|
|
flexible, and faster than having the interpreter do coercion.
|
2000-12-04 22:17:08 -05:00
|
|
|
|
|
|
|
|
2000-12-11 21:00:37 -05:00
|
|
|
Rationale
|
2017-02-02 12:58:49 -05:00
|
|
|
=========
|
2000-12-04 22:17:08 -05:00
|
|
|
|
2017-02-02 12:58:49 -05:00
|
|
|
When implementing numeric or other related operations, it is often
|
|
|
|
desirable to provide not only operations between operands of one type
|
|
|
|
only, e.g. integer + integer, but to generalize the idea behind the
|
|
|
|
operation to other type combinations as well, e.g. integer + float.
|
2000-12-04 22:17:08 -05:00
|
|
|
|
2017-02-02 12:58:49 -05:00
|
|
|
A common approach to this mixed type situation is to provide a method
|
|
|
|
of "lifting" the operands to a common type (coercion) and then use
|
|
|
|
that type's operand method as execution mechanism. Yet, this strategy
|
|
|
|
has a few drawbacks:
|
2000-12-04 22:17:08 -05:00
|
|
|
|
2017-02-02 12:58:49 -05:00
|
|
|
* the "lifting" process creates at least one new (temporary)
|
|
|
|
operand object,
|
2000-12-04 22:17:08 -05:00
|
|
|
|
2017-02-02 12:58:49 -05:00
|
|
|
* since the coercion method is not being told about the operation
|
|
|
|
that is to follow, it is not possible to implement operation
|
|
|
|
specific coercion of types,
|
2000-12-04 22:17:08 -05:00
|
|
|
|
2017-02-02 12:58:49 -05:00
|
|
|
* there is no elegant way to solve situations were a common type
|
|
|
|
is not at hand, and
|
2000-12-04 22:17:08 -05:00
|
|
|
|
2017-02-02 12:58:49 -05:00
|
|
|
* the coercion method will always have to be called prior to the
|
|
|
|
operation's method itself.
|
2000-12-04 22:17:08 -05:00
|
|
|
|
2017-02-02 12:58:49 -05:00
|
|
|
A fix for this situation is obviously needed, since these drawbacks
|
|
|
|
make implementations of types needing these features very cumbersome,
|
|
|
|
if not impossible. As an example, have a look at the ``DateTime`` and
|
|
|
|
``DateTimeDelta`` [1]_ types, the first being absolute, the second
|
|
|
|
relative. You can always add a relative value to an absolute one,
|
|
|
|
giving a new absolute value. Yet, there is no common type which the
|
|
|
|
existing coercion mechanism could use to implement that operation.
|
2000-12-04 22:17:08 -05:00
|
|
|
|
2017-02-02 12:58:49 -05:00
|
|
|
Currently, ``PyInstance`` types are treated specially by the interpreter
|
|
|
|
in that their numeric methods are passed arguments of different types.
|
|
|
|
Removing this special case simplifies the interpreter and allows other
|
|
|
|
types to implement numeric methods that behave like instance types.
|
|
|
|
This is especially useful for extension types like ExtensionClass.
|
2000-12-04 22:17:08 -05:00
|
|
|
|
|
|
|
|
|
|
|
Specification
|
2017-02-02 12:58:49 -05:00
|
|
|
=============
|
|
|
|
|
|
|
|
Instead of using a central coercion method, the process of handling
|
|
|
|
different operand types is simply left to the operation. If the
|
|
|
|
operation finds that it cannot handle the given operand type
|
|
|
|
combination, it may return a special singleton as indicator.
|
|
|
|
|
|
|
|
Note that "numbers" (anything that implements the number protocol, or
|
|
|
|
part of it) written in Python already use the first part of this
|
|
|
|
strategy - it is the C level API that we focus on here.
|
|
|
|
|
|
|
|
To maintain nearly 100% backward compatibility we have to be very
|
|
|
|
careful to make numbers that don't know anything about the new
|
|
|
|
strategy (old style numbers) work just as well as those that expect
|
|
|
|
the new scheme (new style numbers). Furthermore, binary compatibility
|
|
|
|
is a must, meaning that the interpreter may only access and use new
|
|
|
|
style operations if the number indicates the availability of these.
|
|
|
|
|
|
|
|
A new style number is considered by the interpreter as such if and
|
|
|
|
only if it sets the type flag ``Py_TPFLAGS_CHECKTYPES``. The main
|
|
|
|
difference between an old style number and a new style one is that the
|
|
|
|
numeric slot functions can no longer assume to be passed arguments of
|
|
|
|
identical type. New style slots must check all arguments for proper
|
|
|
|
type and implement the necessary conversions themselves. This may seem
|
|
|
|
to cause more work on the behalf of the type implementor, but is in
|
|
|
|
fact no more difficult than writing the same kind of routines for an
|
|
|
|
old style coercion slot.
|
|
|
|
|
|
|
|
If a new style slot finds that it cannot handle the passed argument
|
|
|
|
type combination, it may return a new reference of the special
|
|
|
|
singleton ``Py_NotImplemented`` to the caller. This will cause the caller
|
|
|
|
to try the other operands operation slots until it finds a slot that
|
|
|
|
does implement the operation for the specific type combination. If
|
|
|
|
none of the possible slots succeed, it raises a ``TypeError``.
|
|
|
|
|
|
|
|
To make the implementation easy to understand (the whole topic is
|
|
|
|
esoteric enough), a new layer in the handling of numeric operations is
|
|
|
|
introduced. This layer takes care of all the different cases that need
|
|
|
|
to be taken into account when dealing with all the possible
|
|
|
|
combinations of old and new style numbers. It is implemented by the
|
|
|
|
two static functions ``binary_op()`` and ``ternary_op()``, which are both
|
|
|
|
internal functions that only the functions in Objects/abstract.c
|
|
|
|
have access to. The numeric API (``PyNumber_*``) is easy to adapt to
|
|
|
|
this new layer.
|
|
|
|
|
|
|
|
As a side-effect all numeric slots can be NULL-checked (this has to be
|
|
|
|
done anyway, so the added feature comes at no extra cost).
|
|
|
|
|
|
|
|
|
|
|
|
The scheme used by the layer to execute a binary operation is as
|
|
|
|
follows:
|
|
|
|
|
|
|
|
=== === =================================
|
|
|
|
v w Action taken
|
|
|
|
=== === =================================
|
|
|
|
new new v.op(v,w), w.op(v,w)
|
|
|
|
new old v.op(v,w), coerce(v,w), v.op(v,w)
|
|
|
|
old new w.op(v,w), coerce(v,w), v.op(v,w)
|
|
|
|
old old coerce(v,w), v.op(v,w)
|
|
|
|
=== === =================================
|
|
|
|
|
|
|
|
The indicated action sequence is executed from left to right until
|
|
|
|
either the operation succeeds and a valid result (!=
|
|
|
|
``Py_NotImplemented``) is returned or an exception is raised. Exceptions
|
|
|
|
are returned to the calling function as-is. If a slot returns
|
|
|
|
``Py_NotImplemented``, the next item in the sequence is executed.
|
|
|
|
|
|
|
|
Note that coerce(v,w) will use the old style ``nb_coerce`` slot methods
|
|
|
|
via a call to ``PyNumber_Coerce()``.
|
|
|
|
|
|
|
|
Ternary operations have a few more cases to handle:
|
|
|
|
|
|
|
|
=== === === ====================================================
|
|
|
|
v w z Action taken
|
|
|
|
=== === === ====================================================
|
|
|
|
new new new v.op(v,w,z), w.op(v,w,z), z.op(v,w,z)
|
|
|
|
new old new v.op(v,w,z), z.op(v,w,z), coerce(v,w,z), v.op(v,w,z)
|
|
|
|
old new new w.op(v,w,z), z.op(v,w,z), coerce(v,w,z), v.op(v,w,z)
|
|
|
|
old old new z.op(v,w,z), coerce(v,w,z), v.op(v,w,z)
|
|
|
|
new new old v.op(v,w,z), w.op(v,w,z), coerce(v,w,z), v.op(v,w,z)
|
|
|
|
new old old v.op(v,w,z), coerce(v,w,z), v.op(v,w,z)
|
|
|
|
old new old w.op(v,w,z), coerce(v,w,z), v.op(v,w,z)
|
|
|
|
old old old coerce(v,w,z), v.op(v,w,z)
|
|
|
|
=== === === ====================================================
|
|
|
|
|
|
|
|
The same notes as above, except that coerce(v,w,z) actually does::
|
|
|
|
|
|
|
|
if z != Py_None:
|
|
|
|
coerce(v,w), coerce(v,z), coerce(w,z)
|
|
|
|
else:
|
|
|
|
# treat z as absent variable
|
|
|
|
coerce(v,w)
|
|
|
|
|
|
|
|
|
|
|
|
The current implementation uses this scheme already (there's only one
|
|
|
|
ternary slot: ``nb_pow(a,b,c))``.
|
|
|
|
|
|
|
|
Note that the numeric protocol is also used for some other related
|
|
|
|
tasks, e.g. sequence concatenation. These can also benefit from the
|
|
|
|
new mechanism by implementing right-hand operations for type
|
|
|
|
combinations that would otherwise fail to work. As an example, take
|
|
|
|
string concatenation: currently you can only do string + string. With
|
|
|
|
the new mechanism, a new string-like type could implement new_type +
|
|
|
|
string and string + new_type, even though strings don't know anything
|
|
|
|
about new_type.
|
|
|
|
|
|
|
|
Since comparisons also rely on coercion (every time you compare an
|
|
|
|
integer to a float, the integer is first converted to float and then
|
|
|
|
compared...), a new slot to handle numeric comparisons is needed::
|
|
|
|
|
|
|
|
PyObject *nb_cmp(PyObject *v, PyObject *w)
|
|
|
|
|
|
|
|
This slot should compare the two objects and return an integer object
|
|
|
|
stating the result. Currently, this result integer may only be -1, 0, 1.
|
|
|
|
If the slot cannot handle the type combination, it may return a
|
|
|
|
reference to ``Py_NotImplemented``. [XXX Note that this slot is still
|
|
|
|
in flux since it should take into account rich comparisons
|
2022-01-21 06:03:51 -05:00
|
|
|
(i.e. :pep:`207`).]
|
2017-02-02 12:58:49 -05:00
|
|
|
|
|
|
|
Numeric comparisons are handled by a new numeric protocol API::
|
|
|
|
|
|
|
|
PyObject *PyNumber_Compare(PyObject *v, PyObject *w)
|
|
|
|
|
|
|
|
This function compare the two objects as "numbers" and return an
|
|
|
|
integer object stating the result. Currently, this result integer may
|
|
|
|
only be -1, 0, 1. In case the operation cannot be handled by the given
|
|
|
|
objects, a ``TypeError`` is raised.
|
|
|
|
|
|
|
|
The ``PyObject_Compare()`` API needs to adjusted accordingly to make use
|
|
|
|
of this new API.
|
|
|
|
|
|
|
|
Other changes include adapting some of the built-in functions (e.g.
|
|
|
|
``cmp()``) to use this API as well. Also, ``PyNumber_CoerceEx()`` will need to
|
|
|
|
check for new style numbers before calling the ``nb_coerce`` slot. New
|
|
|
|
style numbers don't provide a coercion slot and thus cannot be
|
|
|
|
explicitly coerced.
|
2000-12-04 22:17:08 -05:00
|
|
|
|
|
|
|
|
|
|
|
Reference Implementation
|
2017-02-02 12:58:49 -05:00
|
|
|
========================
|
2000-12-04 22:17:08 -05:00
|
|
|
|
2017-02-02 12:58:49 -05:00
|
|
|
A preliminary patch for the CVS version of Python is available through
|
|
|
|
the Source Forge patch manager [2]_.
|
2000-12-04 22:17:08 -05:00
|
|
|
|
|
|
|
|
2000-12-05 10:05:14 -05:00
|
|
|
Credits
|
2017-02-02 12:58:49 -05:00
|
|
|
=======
|
2000-12-05 10:05:14 -05:00
|
|
|
|
2017-02-02 12:58:49 -05:00
|
|
|
This PEP and the patch are heavily based on work done by Marc-André
|
|
|
|
Lemburg [3]_.
|
2000-12-05 10:05:14 -05:00
|
|
|
|
|
|
|
|
|
|
|
Copyright
|
2017-02-02 12:58:49 -05:00
|
|
|
=========
|
2000-12-05 10:05:14 -05:00
|
|
|
|
2017-02-02 12:58:49 -05:00
|
|
|
This document has been placed in the public domain.
|
2000-12-05 10:05:14 -05:00
|
|
|
|
|
|
|
|
2000-12-04 22:17:08 -05:00
|
|
|
References
|
2017-02-02 12:58:49 -05:00
|
|
|
==========
|
|
|
|
|
|
|
|
.. [1] http://www.lemburg.com/files/python/mxDateTime.html
|
|
|
|
.. [2] http://sourceforge.net/patch/?func=detailpatch&patch_id=102652&group_id=5470
|
|
|
|
.. [3] http://www.lemburg.com/files/python/CoercionProposal.html
|