2000-07-13 02:33:08 -04:00
|
|
|
|
PEP: 203
|
|
|
|
|
Title: Augmented Assignments
|
|
|
|
|
Version: $Revision$
|
2006-03-23 15:13:19 -05:00
|
|
|
|
Last-Modified: $Date$
|
2000-08-23 01:22:41 -04:00
|
|
|
|
Author: thomas@xs4all.net (Thomas Wouters)
|
2000-09-23 04:19:29 -04:00
|
|
|
|
Status: Final
|
2000-08-23 01:22:41 -04:00
|
|
|
|
Type: Standards Track
|
|
|
|
|
Python-Version: 2.0
|
2000-08-14 11:22:52 -04:00
|
|
|
|
Created: 13-Jul-2000
|
2000-08-14 12:08:44 -04:00
|
|
|
|
Post-History: 14-Aug-2000
|
2000-07-13 02:33:08 -04:00
|
|
|
|
|
2000-07-13 23:30:20 -04:00
|
|
|
|
|
2000-07-16 12:07:29 -04:00
|
|
|
|
Introduction
|
|
|
|
|
|
|
|
|
|
This PEP describes the `augmented assignment' proposal for Python
|
|
|
|
|
2.0. This PEP tracks the status and ownership of this feature,
|
|
|
|
|
slated for introduction in Python 2.0. It contains a description
|
|
|
|
|
of the feature and outlines changes necessary to support the
|
|
|
|
|
feature. This PEP summarizes discussions held in mailing list
|
2000-08-14 15:58:09 -04:00
|
|
|
|
forums, and provides URLs for further information where
|
2000-07-16 12:07:29 -04:00
|
|
|
|
appropriate. The CVS revision history of this file contains the
|
|
|
|
|
definitive historical record.
|
|
|
|
|
|
|
|
|
|
|
2000-08-07 08:40:00 -04:00
|
|
|
|
Proposed semantics
|
2000-07-16 12:07:29 -04:00
|
|
|
|
|
2000-08-07 08:40:00 -04:00
|
|
|
|
The proposed patch that adds augmented assignment to Python
|
|
|
|
|
introduces the following new operators:
|
|
|
|
|
|
|
|
|
|
+= -= *= /= %= **= <<= >>= &= ^= |=
|
|
|
|
|
|
2000-08-14 11:22:52 -04:00
|
|
|
|
They implement the same operator as their normal binary form,
|
|
|
|
|
except that the operation is done `in-place' when the left-hand
|
|
|
|
|
side object supports it, and that the left-hand side is only
|
|
|
|
|
evaluated once.
|
2000-08-07 08:40:00 -04:00
|
|
|
|
|
|
|
|
|
They truly behave as augmented assignment, in that they perform
|
|
|
|
|
all of the normal load and store operations, in addition to the
|
|
|
|
|
binary operation they are intended to do. So, given the expression:
|
|
|
|
|
|
|
|
|
|
x += y
|
|
|
|
|
|
2000-08-14 11:22:52 -04:00
|
|
|
|
The object `x' is loaded, then `y' is added to it, and the
|
|
|
|
|
resulting object is stored back in the original place. The precise
|
|
|
|
|
action performed on the two arguments depends on the type of `x',
|
|
|
|
|
and possibly of `y'.
|
2000-08-07 08:40:00 -04:00
|
|
|
|
|
|
|
|
|
The idea behind augmented assignment in Python is that it isn't
|
|
|
|
|
just an easier way to write the common practice of storing the
|
|
|
|
|
result of a binary operation in its left-hand operand, but also a
|
|
|
|
|
way for the left-hand operand in question to know that it should
|
2000-08-14 11:22:52 -04:00
|
|
|
|
operate `on itself', rather than creating a modified copy of
|
2000-08-07 08:40:00 -04:00
|
|
|
|
itself.
|
|
|
|
|
|
|
|
|
|
To make this possible, a number of new `hooks' are added to Python
|
2000-08-14 15:58:09 -04:00
|
|
|
|
classes and C extension types, which are called when the object in
|
2000-08-07 08:40:00 -04:00
|
|
|
|
question is used as the left hand side of an augmented assignment
|
|
|
|
|
operation. If the class or type does not implement the `in-place'
|
|
|
|
|
hooks, the normal hooks for the particular binary operation are
|
|
|
|
|
used.
|
2000-07-16 12:07:29 -04:00
|
|
|
|
|
2000-08-07 08:40:00 -04:00
|
|
|
|
So, given an instance object `x', the expression
|
2000-07-16 12:07:29 -04:00
|
|
|
|
|
2000-08-07 08:40:00 -04:00
|
|
|
|
x += y
|
|
|
|
|
|
2000-08-23 07:32:01 -04:00
|
|
|
|
tries to call x.__iadd__(y), which is the `in-place' variant of
|
2000-08-25 07:11:25 -04:00
|
|
|
|
__add__. If __iadd__ is not present, x.__add__(y) is attempted,
|
|
|
|
|
and finally y.__radd__(x) if __add__ is missing too. There is no
|
|
|
|
|
`right-hand-side' variant of __iadd__, because that would require
|
|
|
|
|
for `y' to know how to in-place modify `x', which is unsafe to say
|
|
|
|
|
the least. The __iadd__ hook should behave similar to __add__,
|
|
|
|
|
returning the result of the operation (which could be `self')
|
|
|
|
|
which is to be assigned to the variable `x'.
|
2000-08-07 08:40:00 -04:00
|
|
|
|
|
2000-08-14 15:58:09 -04:00
|
|
|
|
For C extension types, the `hooks' are members of the
|
2000-08-25 07:11:25 -04:00
|
|
|
|
PyNumberMethods and PySequenceMethods structures. Some special
|
|
|
|
|
semantics apply to make the use of these methods, and the mixing
|
|
|
|
|
of Python instance objects and C types, as unsurprising as
|
|
|
|
|
possible.
|
|
|
|
|
|
|
|
|
|
In the generic case of `x <augop> y' (or a similar case using the
|
|
|
|
|
PyNumber_InPlace API functions) the principal object being
|
|
|
|
|
operated on is `x'. This differs from normal binary operations,
|
|
|
|
|
where `x' and `y' could be considered `co-operating', because
|
|
|
|
|
unlike in binary operations, the operands in an in-place operation
|
|
|
|
|
cannot be swapped. However, in-place operations do fall back to
|
|
|
|
|
normal binary operations when in-place modification is not
|
|
|
|
|
supported, resuling in the following rules:
|
|
|
|
|
|
|
|
|
|
- If the left-hand object (`x') is an instance object, and it
|
|
|
|
|
has a `__coerce__' method, call that function with `y' as the
|
|
|
|
|
argument. If coercion succeeds, and the resulting left-hand
|
|
|
|
|
object is a different object than `x', stop processing it as
|
|
|
|
|
in-place and call the appropriate function for the normal binary
|
|
|
|
|
operation, with the coerced `x' and `y' as arguments. The result
|
|
|
|
|
of the operation is whatever that function returns.
|
|
|
|
|
|
|
|
|
|
If coercion does not yield a different object for `x', or `x'
|
|
|
|
|
does not define a `__coerce__' method, and `x' has the
|
|
|
|
|
appropriate `__ihook__' for this operation, call that method
|
|
|
|
|
with `y' as the argument, and the result of the operation is
|
|
|
|
|
whatever that method returns.
|
|
|
|
|
|
|
|
|
|
- Otherwise, if the left-hand object is not an instance object,
|
|
|
|
|
but its type does define the in-place function for this
|
|
|
|
|
operation, call that function with `x' and `y' as the arguments,
|
|
|
|
|
and the result of the operation is whatever that function
|
|
|
|
|
returns.
|
|
|
|
|
|
|
|
|
|
Note that no coercion on either `x' or `y' is done in this case,
|
|
|
|
|
and it's perfectly valid for a C type to receive an instance
|
|
|
|
|
object as the second argument; that is something that cannot
|
|
|
|
|
happen with normal binary operations.
|
|
|
|
|
|
|
|
|
|
- Otherwise, process it exactly as a normal binary operation (not
|
|
|
|
|
in-place), including argument coercion. In short, if either
|
|
|
|
|
argument is an instance object, resolve the operation through
|
|
|
|
|
`__coerce__', `__hook__' and `__rhook__'. Otherwise, both
|
|
|
|
|
objects are C types, and they are coerced and passed to the
|
|
|
|
|
appropriate function.
|
|
|
|
|
|
|
|
|
|
- If no way to process the operation can be found, raise a
|
|
|
|
|
TypeError with an error message specific to the operation.
|
|
|
|
|
|
|
|
|
|
- Some special casing exists to account for the case of `+' and
|
|
|
|
|
`*', which have a special meaning for sequences: for `+',
|
|
|
|
|
sequence concatenation, no coercion what so ever is done if a C
|
|
|
|
|
type defines sq_concat or sq_inplace_concat. For `*', sequence
|
|
|
|
|
repeating, `y' is converted to a C integer before calling either
|
|
|
|
|
sq_inplace_repeat and sq_repeat. This is done even if `y' is an
|
|
|
|
|
instance, though not if `x' is an instance.
|
|
|
|
|
|
|
|
|
|
The in-place function should always return a new reference, either
|
|
|
|
|
to the old `x' object if the operation was indeed performed
|
|
|
|
|
in-place, or to a new object.
|
2000-08-07 08:40:00 -04:00
|
|
|
|
|
|
|
|
|
|
2000-08-14 11:22:52 -04:00
|
|
|
|
Rationale
|
|
|
|
|
|
|
|
|
|
There are two main reasons for adding this feature to Python:
|
|
|
|
|
simplicity of expression, and support for in-place operations. The
|
|
|
|
|
end result is a tradeoff between simplicity of syntax and
|
|
|
|
|
simplicity of expression; like most new features, augmented
|
|
|
|
|
assignment doesn't add anything that was previously impossible. It
|
|
|
|
|
merely makes these things easier to do.
|
|
|
|
|
|
2000-08-14 15:58:09 -04:00
|
|
|
|
Adding augmented assignment will make Python's syntax more complex.
|
2000-08-14 11:22:52 -04:00
|
|
|
|
Instead of a single assignment operation, there are now twelve
|
|
|
|
|
assignment operations, eleven of which also perform an binary
|
|
|
|
|
operation. However, these eleven new forms of assignment are easy
|
|
|
|
|
to understand as the coupling between assignment and the binary
|
|
|
|
|
operation, and they require no large conceptual leap to
|
|
|
|
|
understand. Furthermore, languages that do have augmented
|
|
|
|
|
assignment have shown that they are a popular, much used feature.
|
|
|
|
|
Expressions of the form
|
|
|
|
|
|
|
|
|
|
<x> = <x> <operator> <y>
|
|
|
|
|
|
|
|
|
|
are common enough in those languages to make the extra syntax
|
2000-08-14 15:58:09 -04:00
|
|
|
|
worthwhile, and Python does not have significantly fewer of those
|
2000-08-14 11:22:52 -04:00
|
|
|
|
expressions. Quite the opposite, in fact, since in Python you can
|
|
|
|
|
also concatenate lists with a binary operator, something that is
|
|
|
|
|
done quite frequently. Writing the above expression as
|
|
|
|
|
|
|
|
|
|
<x> <operator>= <y>
|
|
|
|
|
|
|
|
|
|
is both more readable and less error prone, because it is
|
|
|
|
|
instantly obvious to the reader that it is <x> that is being
|
|
|
|
|
changed, and not <x> that is being replaced by something almost,
|
|
|
|
|
but not quite, entirely unlike <x>.
|
|
|
|
|
|
|
|
|
|
The new in-place operations are especially useful to matrix
|
|
|
|
|
calculation and other applications that require large objects. In
|
|
|
|
|
order to efficiently deal with the available program memory, such
|
|
|
|
|
packages cannot blindly use the current binary operations. Because
|
2000-08-14 15:58:09 -04:00
|
|
|
|
these operations always create a new object, adding a single item
|
|
|
|
|
to an existing (large) object would result in copying the entire
|
|
|
|
|
object (which may cause the application to run out of memory), add
|
|
|
|
|
the single item, and then possibly delete the original object,
|
|
|
|
|
depending on reference count.
|
2000-08-14 11:22:52 -04:00
|
|
|
|
|
|
|
|
|
To work around this problem, the packages currently have to use
|
|
|
|
|
methods or functions to modify an object in-place, which is
|
2000-08-14 15:58:09 -04:00
|
|
|
|
definitely less readable than an augmented assignment expression.
|
2000-08-14 11:22:52 -04:00
|
|
|
|
Augmented assignment won't solve all the problems for these
|
|
|
|
|
packages, since some operations cannot be expressed in the limited
|
|
|
|
|
set of binary operators to start with, but it is a start. A
|
2000-08-25 07:11:25 -04:00
|
|
|
|
different PEP[2] is looking at adding new operators.
|
2000-08-14 11:22:52 -04:00
|
|
|
|
|
|
|
|
|
|
2000-08-07 08:40:00 -04:00
|
|
|
|
New methods
|
|
|
|
|
|
|
|
|
|
The proposed implementation adds the following 11 possible `hooks'
|
|
|
|
|
which Python classes can implement to overload the augmented
|
|
|
|
|
assignment operations:
|
|
|
|
|
|
2000-08-23 07:32:01 -04:00
|
|
|
|
__iadd__
|
|
|
|
|
__isub__
|
|
|
|
|
__imul__
|
|
|
|
|
__idiv__
|
|
|
|
|
__imod__
|
|
|
|
|
__ipow__
|
|
|
|
|
__ilshift__
|
|
|
|
|
__irshift__
|
|
|
|
|
__iand__
|
|
|
|
|
__ixor__
|
|
|
|
|
__ior__
|
2000-08-07 08:40:00 -04:00
|
|
|
|
|
2000-08-23 07:32:01 -04:00
|
|
|
|
The `i' in `__iadd__' stands for `in-place'.
|
2000-07-16 12:07:29 -04:00
|
|
|
|
|
2000-08-14 15:58:09 -04:00
|
|
|
|
For C extension types, the following struct members are added:
|
2000-07-16 12:07:29 -04:00
|
|
|
|
|
2000-08-07 08:40:00 -04:00
|
|
|
|
To PyNumberMethods:
|
|
|
|
|
binaryfunc nb_inplace_add;
|
|
|
|
|
binaryfunc nb_inplace_subtract;
|
|
|
|
|
binaryfunc nb_inplace_multiply;
|
|
|
|
|
binaryfunc nb_inplace_divide;
|
|
|
|
|
binaryfunc nb_inplace_remainder;
|
|
|
|
|
binaryfunc nb_inplace_power;
|
|
|
|
|
binaryfunc nb_inplace_lshift;
|
|
|
|
|
binaryfunc nb_inplace_rshift;
|
|
|
|
|
binaryfunc nb_inplace_and;
|
|
|
|
|
binaryfunc nb_inplace_xor;
|
|
|
|
|
binaryfunc nb_inplace_or;
|
|
|
|
|
|
|
|
|
|
To PySequenceMethods:
|
|
|
|
|
binaryfunc sq_inplace_concat;
|
|
|
|
|
intargfunc sq_inplace_repeat;
|
|
|
|
|
|
|
|
|
|
In order to keep binary compatibility, the tp_flags TypeObject
|
|
|
|
|
member is used to determine whether the TypeObject in question has
|
|
|
|
|
allocated room for these slots. Until a clean break in binary
|
|
|
|
|
compatibility is made (which may or may not happen before 2.0)
|
|
|
|
|
code that wants to use one of the new struct members must first
|
2000-08-14 11:22:52 -04:00
|
|
|
|
check that they are available with the `PyType_HasFeature()'
|
|
|
|
|
macro:
|
2000-08-07 08:40:00 -04:00
|
|
|
|
|
|
|
|
|
if (PyType_HasFeature(x->ob_type, Py_TPFLAGS_HAVE_INPLACE_OPS) &&
|
|
|
|
|
x->ob_type->tp_as_number && x->ob_type->tp_as_number->nb_inplace_add) {
|
|
|
|
|
/* ... */
|
2000-07-16 12:07:29 -04:00
|
|
|
|
|
2000-08-07 08:40:00 -04:00
|
|
|
|
This check must be made even before testing the method slots for
|
|
|
|
|
NULL values! The macro only tests whether the slots are available,
|
|
|
|
|
not whether they are filled with methods or not.
|
2000-07-16 12:07:29 -04:00
|
|
|
|
|
|
|
|
|
|
2000-08-07 08:40:00 -04:00
|
|
|
|
Implementation
|
|
|
|
|
|
2000-08-25 07:11:25 -04:00
|
|
|
|
The current implementation of augmented assignment[1] adds, in
|
2000-08-14 15:58:09 -04:00
|
|
|
|
addition to the methods and slots already covered, 13 new bytecodes
|
2000-08-07 08:40:00 -04:00
|
|
|
|
and 13 new API functions.
|
|
|
|
|
|
|
|
|
|
The API functions are simply in-place versions of the current
|
|
|
|
|
binary-operation API functions:
|
|
|
|
|
|
|
|
|
|
PyNumber_InPlaceAdd(PyObject *o1, PyObject *o2);
|
|
|
|
|
PyNumber_InPlaceSubtract(PyObject *o1, PyObject *o2);
|
|
|
|
|
PyNumber_InPlaceMultiply(PyObject *o1, PyObject *o2);
|
|
|
|
|
PyNumber_InPlaceDivide(PyObject *o1, PyObject *o2);
|
|
|
|
|
PyNumber_InPlaceRemainder(PyObject *o1, PyObject *o2);
|
|
|
|
|
PyNumber_InPlacePower(PyObject *o1, PyObject *o2);
|
|
|
|
|
PyNumber_InPlaceLshift(PyObject *o1, PyObject *o2);
|
|
|
|
|
PyNumber_InPlaceRshift(PyObject *o1, PyObject *o2);
|
|
|
|
|
PyNumber_InPlaceAnd(PyObject *o1, PyObject *o2);
|
|
|
|
|
PyNumber_InPlaceXor(PyObject *o1, PyObject *o2);
|
|
|
|
|
PyNumber_InPlaceOr(PyObject *o1, PyObject *o2);
|
|
|
|
|
PySequence_InPlaceConcat(PyObject *o1, PyObject *o2);
|
|
|
|
|
PySequence_InPlaceRepeat(PyObject *o, int count);
|
|
|
|
|
|
|
|
|
|
They call either the Python class hooks (if either of the objects
|
|
|
|
|
is a Python class instance) or the C type's number or sequence
|
|
|
|
|
methods.
|
|
|
|
|
|
|
|
|
|
The new bytecodes are:
|
|
|
|
|
INPLACE_ADD
|
|
|
|
|
INPLACE_SUBTRACT
|
|
|
|
|
INPLACE_MULTIPLY
|
|
|
|
|
INPLACE_DIVIDE
|
|
|
|
|
INPLACE_REMAINDER
|
|
|
|
|
INPLACE_POWER
|
|
|
|
|
INPLACE_LEFTSHIFT
|
|
|
|
|
INPLACE_RIGHTSHIFT
|
|
|
|
|
INPLACE_AND
|
|
|
|
|
INPLACE_XOR
|
|
|
|
|
INPLACE_OR
|
|
|
|
|
ROT_FOUR
|
|
|
|
|
DUP_TOPX
|
|
|
|
|
|
|
|
|
|
The INPLACE_* bytecodes mirror the BINARY_* bytecodes, except that
|
2000-08-14 11:22:52 -04:00
|
|
|
|
they are implemented as calls to the `InPlace' API functions. The
|
|
|
|
|
other two bytecodes are `utility' bytecodes: ROT_FOUR behaves like
|
2000-08-07 08:40:00 -04:00
|
|
|
|
ROT_THREE except that the four topmost stack items are rotated.
|
2000-07-16 12:07:29 -04:00
|
|
|
|
|
2000-08-07 08:40:00 -04:00
|
|
|
|
DUP_TOPX is a bytecode that takes a single argument, which should
|
|
|
|
|
be an integer between 1 and 5 (inclusive) which is the number of
|
|
|
|
|
items to duplicate in one block. Given a stack like this (where
|
2000-08-14 11:22:52 -04:00
|
|
|
|
the right side of the list is the `top' of the stack):
|
2000-07-16 12:07:29 -04:00
|
|
|
|
|
2000-08-14 11:22:52 -04:00
|
|
|
|
[1, 2, 3, 4, 5]
|
2000-08-07 08:40:00 -04:00
|
|
|
|
|
|
|
|
|
"DUP_TOPX 3" would duplicate the top 3 items, resulting in this
|
|
|
|
|
stack:
|
|
|
|
|
|
2000-08-14 11:22:52 -04:00
|
|
|
|
[1, 2, 3, 4, 5, 3, 4, 5]
|
2000-08-07 08:40:00 -04:00
|
|
|
|
|
|
|
|
|
DUP_TOPX with an argument of 1 is the same as DUP_TOP. The limit
|
|
|
|
|
of 5 is purely an implementation limit. The implementation of
|
|
|
|
|
augmented assignment requires only DUP_TOPX with an argument of 2
|
|
|
|
|
and 3, and could do without this new opcode at the cost of a fair
|
|
|
|
|
number of DUP_TOP and ROT_*.
|
|
|
|
|
|
|
|
|
|
|
2000-08-14 11:22:52 -04:00
|
|
|
|
Open Issues
|
|
|
|
|
|
2000-08-23 07:32:01 -04:00
|
|
|
|
The PyNumber_InPlace API is only a subset of the normal PyNumber
|
|
|
|
|
API: only those functions that are required to support the
|
|
|
|
|
augmented assignment syntax are included. If other in-place API
|
|
|
|
|
functions are needed, they can be added later.
|
2000-08-14 11:22:52 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
The DUP_TOPX bytecode is a conveniency bytecode, and is not
|
|
|
|
|
actually necessary. It should be considered whether this bytecode
|
|
|
|
|
is worth having. There seems to be no other possible use for this
|
|
|
|
|
bytecode at this time.
|
|
|
|
|
|
|
|
|
|
|
2000-08-07 08:40:00 -04:00
|
|
|
|
Copyright
|
|
|
|
|
|
|
|
|
|
This document has been placed in the public domain.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
References
|
2000-07-16 12:07:29 -04:00
|
|
|
|
|
2000-08-07 08:40:00 -04:00
|
|
|
|
[1] http://www.python.org/pipermail/python-list/2000-June/059556.html
|
2001-07-05 15:09:19 -04:00
|
|
|
|
|
2000-08-23 07:32:01 -04:00
|
|
|
|
[2] http://sourceforge.net/patch?func=detailpatch&patch_id=100699&group_id=5470
|
2001-07-05 15:09:19 -04:00
|
|
|
|
|
|
|
|
|
[3] PEP 211, Adding A New Outer Product Operator, Wilson
|
|
|
|
|
http://www.python.org/peps/pep-0211.html
|
2000-07-16 12:07:29 -04:00
|
|
|
|
|
2000-08-23 01:22:41 -04:00
|
|
|
|
|
2000-07-13 02:33:08 -04:00
|
|
|
|
|
|
|
|
|
Local Variables:
|
|
|
|
|
mode: indented-text
|
|
|
|
|
indent-tabs-mode: nil
|
|
|
|
|
End:
|