
I take it you rather see a 'AUGOP' bytecode than a 'GETSET_<type>' bytecode, that does the loading, operation and storing ? Not that I disagree, not at all, I just want to have that clear ;)
Yes, mostly because there are so many variants based on the kind of loading and storing: for each load-type operator you need 11 GETSET operators, and there are many different load operators: local, global, unspecified (== local by dict instead), by attribute, by index, or by slice... I am still neutral on the choice between a single AUGOP with an argument that takes an argument specifying the opcode, and 11 new operators: AUGASS_ADD, ... (Actually, the latter seem the most logical given that we also have BINARY_ADD, ...)
Here's what happens now:
(a, b, c) += 1,2,3 SyntaxError: no augmented assignment to tuple
(Isn't a reference implementation a cool thing ? ;)
Very nice. Don't touch that part.
Second, what should happen to a slice assignment? The basic slice form is:
a[i:j] += b
but there are others: Python's slice syntax allows an arbitrary [yadah]
What's so special about slice assignment ?
The only special thing about slice assignment is that there are 12 extra opcodes to deal with slices in the non-augmented-assignment case. I was making a case for avoiding this explosion, but somehow I got lost in my own train of thought. :(
You yourself (though Tim<wink>) suggested the following:
x += y
is effectively (minus order and number of evaluations)
x = x.__add_ab__(y)
Where __add_ab__() is the Python method, if 'x' is a python class, or plain '__add__' if 'x' doesn't have an '__add_ab__', or 'y.__radd__' if x doesn't have __add__. Similarly, if 'x' is a builtin, tp_as_number->nb_inplace_add is used, or tp_as_number->nb_add, etc. (Or sq_concat, if it's a seq and the operator is add.)
The __add_ab__ method, or the C equivalent, can decide for itself whether it should return 'self', thus making the operation truly in-place, or a new instance of an object. This may seem as a complicated and unexpected way to do things, but it makes it possible to transparently support __add__ and __radd__ too, because they already return new objects.
And you (through Tim) literally scolded me for trying to suggest
x[1:10] += y
being something like
x = x.__add_slice_ab__(y, slice(1,10))
Instead, it should become
x[1:10] = x[1:10] + y
(Or rather, to keep the same order of evaluation:)
tmp1 = y # in case of a more complicated expression tmp2 = x[1:10] x[1:10] = tmp2.__add_ab__(tmp1)
The whole story about how complicated, convoluted and confusing Python's slicing is, though interesting and correct, is not connected to augmented assignment ;)
a[:, ..., ::, 0:10:2, :10:, 1, 2:, ::-1] += 1
Becomes
tmp1 = a[:, ..., ::, 0:10:2, :10:, 1, 2:, ::-1] a[:, ..., ::, 0:10:2, :10:, 1, 2:, ::-1] = tmp1.__add_ab__(1)
Yes. I should have realized earlier on that my pseudo code was just spelling out the necessary DUP etc. opcodes to avoid calculating the location of 'a' or the index(es) twice. Looking at my pseudo code for "a[i:j] += b" again, I realize that there's no reason to treat basic slices different than in other contexts -- it can be done with just DUP, ROT3 and ROT4: a[i:j] += b LOAD a [a] DUP [a, a] LOAD i [a, a, i] DUP [a, a, i, i] ROT3 [a, i, a, i] LOAD j [a, i, a, i, j] DUP [a, i, a, i, j, j] ROT4 [a, i, j, a, i, j] GETSLICE [a, i, j, slice] LOAD b [a, i, j, slice, b] AUGADD [a, i, j, result] SETSLICE [] So we need a new opcode ROT4 (or ROT_FOUR to be consistent in the spelling).
It would be nice if the SLICE bytecodes were removed altogether and instead slice() objects would be created for all slices, even basic ones. (I believe this was proposed in this list at some point.) The original SLICE opcodes were introduced in ancient times, when basic slices were the only accepted slice syntax.
Agreed, however! You can't just make all slices use a sliceobject, for two reasons: a C extention type can use both the sequence and the mapping interface, and might not expect basic slices to be turned into a slice object. For instance, it's mapping interface may raise a TypeError, "Slices not supported", when it encounters a slice. Choosing mapping over sequence methods may break all that code. The same goes for Python classes.
That's right. The backwards compatibility issues are the most daunting.
The other reason is usability. The current __get_slice__(self, start, end) sytnax is very convenient for nearly all uses of slices. Possibly because the builtin types don't handle extended slices currently, and possibly because the slice-object way is not generally known. (Just recently, there was someone asking on python-list on how to use slices. I'm not sure if he got a proper answer, but I've never seen such questions on the subject of basic slices!)
If we do get a new __get_slice__ that handles all slices, I suggest doing it like this:
class X: def __get_newslice__(self, (start, end, step)):
where the type of start, end and step is not defined. This can be done with slice objects and sequence-unpacking, of course, but it makes it a lot more usable.
Another alternative that tries to preserve compatibility is to call __getslice__(self, start, end) when the step is None, but __getslice__(self, start, end, step) when it isn't. Old code will raise a reasonable exception when a step is specified, while step-aware code can specify the step as a default argument value of 1 or None. --Guido van Rossum (home page: http://www.pythonlabs.com/~guido/)