[Python-Dev] augmented assignment

Thomas Wouters thomas@xs4all.net
Mon, 7 Aug 2000 15:07:11 +0200


--0OAP2g/MAC+5xKAE
Content-Type: text/plain; charset=us-ascii


I 'finished' the new augmented assignment patch yesterday, following the
suggestions made by Guido about using INPLACE_* bytecodes rather than
special GETSET_* opcodes.

I ended up with 13 new opcodes: INPLACE_* opcodes for the 11 binary
operation opcodes, DUP_TOPX which duplicates a number of stack items instead
of just the topmost item, and ROT_FOUR.

I thought I didn't need ROT_FOUR if we had DUP_TOPX but I hadn't realized
assignment needs the new value at the bottom of the 'stack', and the objects
that are used in the assignment above that. So ROT_FOUR is necessary in the
case of slice-assignment:

a[b:c] += i

LOAD a			[a]
LOAD b			[a, b]
LOAD c			[a, b, c]
DUP_TOPX 3		[a, b, c, a, b, c]
SLICE+3			[a, b, c, a[b:c]]
LOAD i			[a, b, c, a[b:c], i]
INPLACE_ADD		[a, b, c, result]
ROT_FOUR		[result, a, b, c]
STORE_SLICE+3		[]

When (and if) the *SLICE opcodes are removed, ROT_FOUR can, too :)

The patch is 'done' in my opinion, except for two tiny things:

- PyNumber_InPlacePower() takes just two arguments, not three. Three
argument power() does such 'orrible things to coerce all the arguments, and
you can't do augmented-assignment-three-argument-power anyway. If it's added
it would be for the API only, and I'm not sure if it's worth it :P

- I still don't like the '_ab_' names :) I think __inplace_add__ or __iadd__
  is better, but that's just me.

The PEP is also 'done'. Feedback is more than welcome, including spelling
fixes and the like. I've attached the PEP to this mail, for convenience.

-- 
Thomas Wouters <thomas@xs4all.net>

Hi! I'm a .signature virus! copy me into your .signature file to help me spread!

--0OAP2g/MAC+5xKAE
Content-Type: text/plain
Content-Disposition: attachment; filename="pep-0203.txt"

PEP: 203
Title: Augmented Assignments
Version: $Revision: 1.4 $
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:

--0OAP2g/MAC+5xKAE--