219 lines
7.9 KiB
Plaintext
219 lines
7.9 KiB
Plaintext
PEP: 203
|
||
Title: Augmented Assignments
|
||
Version: $Revision$
|
||
Owner: thomas@xs4all.net (Thomas Wouters)
|
||
Python-Version: 2.0
|
||
Status: Draft
|
||
|
||
|
||
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
|
||
forums, and provides URLs for further information, where
|
||
appropriate. The CVS revision history of this file contains the
|
||
definitive historical record.
|
||
|
||
|
||
Proposed semantics
|
||
|
||
The proposed patch that adds augmented assignment to Python
|
||
introduces the following new operators:
|
||
|
||
+= -= *= /= %= **= <<= >>= &= ^= |=
|
||
|
||
They implement the same operator as their normal binary form, with
|
||
the exception that the operation is done `in-place' whenever
|
||
possible.
|
||
|
||
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
|
||
|
||
The object `x' is loaded, then added with 1, 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'.
|
||
|
||
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
|
||
operate 'on itself', rather than creating a modified copy of
|
||
itself.
|
||
|
||
To make this possible, a number of new `hooks' are added to Python
|
||
classes and C extention types, which are called when the object in
|
||
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.
|
||
|
||
So, given an instance object `x', the expression
|
||
|
||
x += y
|
||
|
||
tries to call x.__add_ab__(y), which is the 'in-place' variant of
|
||
__add__. If __add_ab__ 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 __add_ab__, because that
|
||
would require for `y' to know how to in-place modify `x', which is
|
||
an unsafe assumption. The __add_ab__ hook should behave exactly
|
||
like __add__, returning the result of the operation (which could
|
||
be `self') which is to be stored in the variable `x'.
|
||
|
||
For C extention types, the `hooks' are members of the
|
||
PyNumberMethods and PySequenceMethods structures, and are called
|
||
in exactly the same manner as the existing non-inplace operations,
|
||
including argument coercion. C methods should also take care to
|
||
return a new reference to the result object, whether it's the same
|
||
object or a new one. So if the original object is returned, it
|
||
should be INCREF()'d appropriately.
|
||
|
||
|
||
New methods
|
||
|
||
The proposed implementation adds the following 11 possible `hooks'
|
||
which Python classes can implement to overload the augmented
|
||
assignment operations:
|
||
|
||
__add_ab__
|
||
__sub_ab__
|
||
__mul_ab__
|
||
__div_ab__
|
||
__mod_ab__
|
||
__pow_ab__
|
||
__lshift_ab__
|
||
__rshift_ab__
|
||
__and_ab__
|
||
__xor_ab__
|
||
__or_ab__
|
||
|
||
The `__add_ab__' name is one proposed by Guido[1], and stands for `and
|
||
becomes'. Other proposed names include '__iadd__', `__add_in__'
|
||
`__inplace_add__'
|
||
|
||
For C extention types, the following struct members are added:
|
||
|
||
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
|
||
check that they are available with the 'PyType_HasFeature()' macro:
|
||
|
||
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) {
|
||
/* ... */
|
||
|
||
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.
|
||
|
||
|
||
Implementation
|
||
|
||
The current implementation of augmented assignment[2] adds, in
|
||
addition to the methods and slots alread covered, 13 new bytecodes
|
||
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
|
||
they are implemented as calls to the 'InPlace' API functions. The
|
||
other two bytecodes are 'utility' bytecodes: ROT_FOUR behaves like
|
||
ROT_THREE except that the four topmost stack items are rotated.
|
||
|
||
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
|
||
the left side of the list is the 'top' of the stack):
|
||
|
||
[a, b, c, d, e, f, g]
|
||
|
||
"DUP_TOPX 3" would duplicate the top 3 items, resulting in this
|
||
stack:
|
||
|
||
[a, b, c, d, e, f, g, e, f, g]
|
||
|
||
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_*.
|
||
|
||
|
||
Copyright
|
||
|
||
This document has been placed in the public domain.
|
||
|
||
|
||
References
|
||
|
||
[1] http://www.python.org/pipermail/python-list/2000-June/059556.html
|
||
[2]
|
||
http://sourceforge.net/patch?func=detailpatch&patch_id=100699&group_id=5470
|
||
|
||
|
||
|
||
Local Variables:
|
||
mode: indented-text
|
||
indent-tabs-mode: nil
|
||
End:
|