Apply SF patch #101029: call __getitem__ with a proper slice object if there
is no __getslice__ available. Also does the same for C extension types. Includes rudimentary documentation (it could use a cross reference to the section on slice objects, I couldn't figure out how to do that) and a test suite for all Python __hooks__ I could think of, including the new behaviour.
This commit is contained in:
parent
68add2e938
commit
1d75a79c00
|
@ -1042,11 +1042,12 @@ objects. The first set of methods is used either to emulate a
|
|||
sequence or to emulate a mapping; the difference is that for a
|
||||
sequence, the allowable keys should be the integers \var{k} for which
|
||||
\code{0 <= \var{k} < \var{N}} where \var{N} is the length of the
|
||||
sequence, and the method \method{__getslice__()} (see below) should be
|
||||
defined. It is also recommended that mappings provide methods
|
||||
\method{keys()}, \method{values()}, \method{items()},
|
||||
\method{has_key()}, \method{get()}, \method{clear()}, \method{copy()},
|
||||
and \method{update()} behaving similar to those for
|
||||
sequence, or slice objects, which define a range of items. (For backwards
|
||||
compatibility, the method \method{__getslice__()} (see below) can also be
|
||||
defined to handle simple, but not extended slices.) It is also recommended
|
||||
that mappings provide methods \method{keys()}, \method{values()},
|
||||
\method{items()}, \method{has_key()}, \method{get()}, \method{clear()},
|
||||
\method{copy()}, and \method{update()} behaving similar to those for
|
||||
Python's standard dictionary objects; mutable sequences should provide
|
||||
methods \method{append()}, \method{count()}, \method{index()},
|
||||
\method{insert()}, \method{pop()}, \method{remove()}, \method{reverse()}
|
||||
|
@ -1141,22 +1142,30 @@ If the instance does not implement the \method{__len__()} method, an
|
|||
No guarantee is made that indexes adjusted this way are not still
|
||||
negative. Indexes which are greater than the length of the sequence
|
||||
are not modified.
|
||||
This method is deprecated. If no \method{__getslice__()} is found, a slice
|
||||
object is created instead, and passed to \method{__getitem__()} instead.
|
||||
\end{methoddesc}
|
||||
|
||||
\begin{methoddesc}[sequence object]{__setslice__}{self, i, j, sequence}
|
||||
Called to implement assignment to \code{\var{self}[\var{i}:\var{j}]}.
|
||||
Same notes for \var{i} and \var{j} as for \method{__getslice__()}.
|
||||
|
||||
This method is deprecated. If no \method{__setslice__()} is found, a slice
|
||||
object is created instead, and passed to \method{__setitem__()} instead.
|
||||
\end{methoddesc}
|
||||
|
||||
\begin{methoddesc}[sequence object]{__delslice__}{self, i, j}
|
||||
Called to implement deletion of \code{\var{self}[\var{i}:\var{j}]}.
|
||||
Same notes for \var{i} and \var{j} as for \method{__getslice__()}.
|
||||
This method is deprecated. If no \method{__delslice__()} is found, a slice
|
||||
object is created instead, and passed to \method{__delitem__()} instead.
|
||||
\end{methoddesc}
|
||||
|
||||
Notice that these methods are only invoked when a single slice with a
|
||||
single colon is used. For slice operations involving extended slice
|
||||
notation, \method{__getitem__()}, \method{__setitem__()}
|
||||
or\method{__delitem__()} is called.
|
||||
Notice that these methods are only invoked when a single slice with a single
|
||||
colon is used, and the slice method is available. For slice operations
|
||||
involving extended slice notation, or in absence of the slice methods,
|
||||
\method{__getitem__()}, \method{__setitem__()} or \method{__delitem__()} is
|
||||
called with a slice object as argument.
|
||||
|
||||
|
||||
\subsection{Emulating numeric types\label{numeric-types}}
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
test_class
|
||||
__init__: ()
|
||||
__coerce__: (1,)
|
||||
__add__: (1,)
|
||||
__coerce__: (1,)
|
||||
__radd__: (1,)
|
||||
__coerce__: (1,)
|
||||
__sub__: (1,)
|
||||
__coerce__: (1,)
|
||||
__rsub__: (1,)
|
||||
__coerce__: (1,)
|
||||
__mul__: (1,)
|
||||
__coerce__: (1,)
|
||||
__rmul__: (1,)
|
||||
__coerce__: (1,)
|
||||
__div__: (1,)
|
||||
__coerce__: (1,)
|
||||
__rdiv__: (1,)
|
||||
__coerce__: (1,)
|
||||
__mod__: (1,)
|
||||
__coerce__: (1,)
|
||||
__rmod__: (1,)
|
||||
__coerce__: (1,)
|
||||
__divmod__: (1,)
|
||||
__coerce__: (1,)
|
||||
__rdivmod__: (1,)
|
||||
__coerce__: (1,)
|
||||
__pow__: (1,)
|
||||
__coerce__: (1,)
|
||||
__rpow__: (1,)
|
||||
__coerce__: (1,)
|
||||
__rshift__: (1,)
|
||||
__coerce__: (1,)
|
||||
__rrshift__: (1,)
|
||||
__coerce__: (1,)
|
||||
__lshift__: (1,)
|
||||
__coerce__: (1,)
|
||||
__rlshift__: (1,)
|
||||
__coerce__: (1,)
|
||||
__and__: (1,)
|
||||
__coerce__: (1,)
|
||||
__rand__: (1,)
|
||||
__coerce__: (1,)
|
||||
__or__: (1,)
|
||||
__coerce__: (1,)
|
||||
__ror__: (1,)
|
||||
__coerce__: (1,)
|
||||
__xor__: (1,)
|
||||
__coerce__: (1,)
|
||||
__rxor__: (1,)
|
||||
__contains__: (1,)
|
||||
__getitem__: (1,)
|
||||
__setitem__: (1, 1)
|
||||
__delitem__: (1,)
|
||||
__getslice__: (0, 42)
|
||||
__setslice__: (0, 42, 'The Answer')
|
||||
__delslice__: (0, 42)
|
||||
__getitem__: (slice(2, 1024, 10),)
|
||||
__setitem__: (slice(2, 1024, 10), 'A lot')
|
||||
__delitem__: (slice(2, 1024, 10),)
|
||||
__getitem__: ((slice(None, 42, None), Ellipsis, slice(None, 24, None), 24, 100),)
|
||||
__setitem__: ((slice(None, 42, None), Ellipsis, slice(None, 24, None), 24, 100), 'Strange')
|
||||
__delitem__: ((slice(None, 42, None), Ellipsis, slice(None, 24, None), 24, 100),)
|
||||
__getitem__: (slice(0, 42, None),)
|
||||
__setitem__: (slice(0, 42, None), 'The Answer')
|
||||
__delitem__: (slice(0, 42, None),)
|
||||
__neg__: ()
|
||||
__pos__: ()
|
||||
__abs__: ()
|
||||
__int__: ()
|
||||
__long__: ()
|
||||
__float__: ()
|
||||
__oct__: ()
|
||||
__hex__: ()
|
||||
__hash__: ()
|
||||
__repr__: ()
|
||||
__str__: ()
|
||||
__coerce__: (1,)
|
||||
__cmp__: (1,)
|
||||
__coerce__: (1,)
|
||||
__cmp__: (1,)
|
||||
__coerce__: (1,)
|
||||
__cmp__: (1,)
|
||||
__coerce__: (1,)
|
||||
__cmp__: (1,)
|
||||
__coerce__: (1,)
|
||||
__cmp__: (1,)
|
||||
__coerce__: (1,)
|
||||
__cmp__: (1,)
|
||||
__coerce__: (1,)
|
||||
__cmp__: (1,)
|
||||
__coerce__: (1,)
|
||||
__cmp__: (1,)
|
||||
__coerce__: (1,)
|
||||
__cmp__: (1,)
|
||||
__coerce__: (1,)
|
||||
__cmp__: (1,)
|
||||
__del__: ()
|
||||
__getattr__: ('spam',)
|
||||
__setattr__: ('eggs', 'spam, spam, spam and ham')
|
||||
__delattr__: ('cardinal',)
|
|
@ -0,0 +1,219 @@
|
|||
"Test the functionality of Python classes implementing operators."
|
||||
|
||||
|
||||
testmeths = [
|
||||
|
||||
# Binary operations
|
||||
"add",
|
||||
"radd",
|
||||
"sub",
|
||||
"rsub",
|
||||
"mul",
|
||||
"rmul",
|
||||
"div",
|
||||
"rdiv",
|
||||
"mod",
|
||||
"rmod",
|
||||
"divmod",
|
||||
"rdivmod",
|
||||
"pow",
|
||||
"rpow",
|
||||
"rshift",
|
||||
"rrshift",
|
||||
"lshift",
|
||||
"rlshift",
|
||||
"and",
|
||||
"rand",
|
||||
"or",
|
||||
"ror",
|
||||
"xor",
|
||||
"rxor",
|
||||
|
||||
# List/dict operations
|
||||
"contains",
|
||||
"getitem",
|
||||
"getslice",
|
||||
"setitem",
|
||||
"setslice",
|
||||
"delitem",
|
||||
"delslice",
|
||||
|
||||
# Unary operations
|
||||
"neg",
|
||||
"pos",
|
||||
"abs",
|
||||
"int",
|
||||
"long",
|
||||
"float",
|
||||
"oct",
|
||||
"hex",
|
||||
|
||||
# generic operations
|
||||
"init",
|
||||
"del",
|
||||
]
|
||||
|
||||
# These need to return something other than None
|
||||
# "coerce",
|
||||
# "hash",
|
||||
# "str",
|
||||
# "repr",
|
||||
|
||||
# These are separate because they can influence the test of other methods.
|
||||
# "getattr",
|
||||
# "setattr",
|
||||
# "delattr",
|
||||
|
||||
class AllTests:
|
||||
def __coerce__(self, *args):
|
||||
print "__coerce__:", args
|
||||
return (self,) + args
|
||||
|
||||
def __hash__(self, *args):
|
||||
print "__hash__:", args
|
||||
return id(self)
|
||||
|
||||
def __str__(self, *args):
|
||||
print "__str__:", args
|
||||
return "AllTests"
|
||||
|
||||
def __repr__(self, *args):
|
||||
print "__repr__:", args
|
||||
return "AllTests"
|
||||
|
||||
def __cmp__(self, *args):
|
||||
print "__cmp__:", args
|
||||
return 0
|
||||
|
||||
for method in testmeths:
|
||||
exec("""def __%(method)s__(self, *args):
|
||||
print "__%(method)s__:", args
|
||||
"""%locals(), AllTests.__dict__);
|
||||
|
||||
# this also tests __init__ of course.
|
||||
testme = AllTests()
|
||||
|
||||
# Binary operations
|
||||
|
||||
testme + 1
|
||||
1 + testme
|
||||
|
||||
testme - 1
|
||||
1 - testme
|
||||
|
||||
testme * 1
|
||||
1 * testme
|
||||
|
||||
testme / 1
|
||||
1 / testme
|
||||
|
||||
testme % 1
|
||||
1 % testme
|
||||
|
||||
divmod(testme,1)
|
||||
divmod(1, testme)
|
||||
|
||||
testme ** 1
|
||||
1 ** testme
|
||||
|
||||
testme >> 1
|
||||
1 >> testme
|
||||
|
||||
testme << 1
|
||||
1 << testme
|
||||
|
||||
testme & 1
|
||||
1 & testme
|
||||
|
||||
testme | 1
|
||||
1 | testme
|
||||
|
||||
testme ^ 1
|
||||
1 ^ testme
|
||||
|
||||
|
||||
# List/dict operations
|
||||
|
||||
1 in testme
|
||||
|
||||
testme[1]
|
||||
testme[1] = 1
|
||||
del testme[1]
|
||||
|
||||
testme[:42]
|
||||
testme[:42] = "The Answer"
|
||||
del testme[:42]
|
||||
|
||||
testme[2:1024:10]
|
||||
testme[2:1024:10] = "A lot"
|
||||
del testme[2:1024:10]
|
||||
|
||||
testme[:42, ..., :24:, 24, 100]
|
||||
testme[:42, ..., :24:, 24, 100] = "Strange"
|
||||
del testme[:42, ..., :24:, 24, 100]
|
||||
|
||||
|
||||
# Now remove the slice hooks to see if converting normal slices to slice
|
||||
# object works.
|
||||
|
||||
del AllTests.__getslice__
|
||||
del AllTests.__setslice__
|
||||
del AllTests.__delslice__
|
||||
|
||||
testme[:42]
|
||||
testme[:42] = "The Answer"
|
||||
del testme[:42]
|
||||
|
||||
|
||||
# Unary operations
|
||||
|
||||
-testme
|
||||
+testme
|
||||
abs(testme)
|
||||
int(testme)
|
||||
long(testme)
|
||||
float(testme)
|
||||
oct(testme)
|
||||
hex(testme)
|
||||
|
||||
|
||||
# And the rest...
|
||||
|
||||
hash(testme)
|
||||
repr(testme)
|
||||
str(testme)
|
||||
|
||||
testme == 1
|
||||
testme < 1
|
||||
testme > 1
|
||||
testme <> 1
|
||||
testme != 1
|
||||
1 == testme
|
||||
1 < testme
|
||||
1 > testme
|
||||
1 <> testme
|
||||
1 != testme
|
||||
|
||||
# This test has to be last (duh.)
|
||||
|
||||
del testme
|
||||
|
||||
|
||||
# Interfering tests
|
||||
|
||||
class ExtraTests:
|
||||
def __getattr__(self, *args):
|
||||
print "__getattr__:", args
|
||||
return "SomeVal"
|
||||
|
||||
def __setattr__(self, *args):
|
||||
print "__setattr__:", args
|
||||
|
||||
def __delattr__(self, *args):
|
||||
print "__delattr__:", args
|
||||
|
||||
testme = ExtraTests()
|
||||
testme.spam
|
||||
testme.eggs = "spam, spam, spam and ham"
|
||||
del testme.cardinal
|
||||
|
|
@ -876,10 +876,29 @@ PySequence_GetItem(PyObject *s, int i)
|
|||
return type_error("unindexable object");
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
sliceobj_from_intint(int i, int j)
|
||||
{
|
||||
PyObject *start, *end, *slice;
|
||||
start = PyInt_FromLong((long)i);
|
||||
if (!start)
|
||||
return NULL;
|
||||
end = PyInt_FromLong((long)j);
|
||||
if (!end) {
|
||||
Py_DECREF(start);
|
||||
return NULL;
|
||||
}
|
||||
slice = PySlice_New(start, end, NULL);
|
||||
Py_DECREF(start);
|
||||
Py_DECREF(end);
|
||||
return slice;
|
||||
}
|
||||
|
||||
PyObject *
|
||||
PySequence_GetSlice(PyObject *s, int i1, int i2)
|
||||
{
|
||||
PySequenceMethods *m;
|
||||
PyMappingMethods *mp;
|
||||
|
||||
if (!s) return null_error();
|
||||
|
||||
|
@ -897,6 +916,14 @@ PySequence_GetSlice(PyObject *s, int i1, int i2)
|
|||
}
|
||||
}
|
||||
return m->sq_slice(s, i1, i2);
|
||||
} else if ((mp = s->ob_type->tp_as_mapping) && mp->mp_subscript) {
|
||||
PyObject *res;
|
||||
PyObject *slice = sliceobj_from_intint(i1, i2);
|
||||
if (!slice)
|
||||
return NULL;
|
||||
res = mp->mp_subscript(s, slice);
|
||||
Py_DECREF(slice);
|
||||
return res;
|
||||
}
|
||||
|
||||
return type_error("unsliceable object");
|
||||
|
@ -960,6 +987,7 @@ int
|
|||
PySequence_SetSlice(PyObject *s, int i1, int i2, PyObject *o)
|
||||
{
|
||||
PySequenceMethods *m;
|
||||
PyMappingMethods *mp;
|
||||
|
||||
if (s == NULL) {
|
||||
null_error();
|
||||
|
@ -980,7 +1008,16 @@ PySequence_SetSlice(PyObject *s, int i1, int i2, PyObject *o)
|
|||
}
|
||||
}
|
||||
return m->sq_ass_slice(s, i1, i2, o);
|
||||
} else if ((mp = s->ob_type->tp_as_mapping) && mp->mp_ass_subscript) {
|
||||
int res;
|
||||
PyObject *slice = sliceobj_from_intint(i1, i2);
|
||||
if (!slice)
|
||||
return -1;
|
||||
res = mp->mp_ass_subscript(s, slice, o);
|
||||
Py_DECREF(slice);
|
||||
return res;
|
||||
}
|
||||
|
||||
type_error("object doesn't support slice assignment");
|
||||
return -1;
|
||||
}
|
||||
|
|
|
@ -971,6 +971,27 @@ instance_item(PyInstanceObject *inst, int i)
|
|||
return res;
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
sliceobj_from_intint(int i, int j)
|
||||
{
|
||||
PyObject *start, *end, *res;
|
||||
|
||||
start = PyInt_FromLong((long)i);
|
||||
if (!start)
|
||||
return NULL;
|
||||
|
||||
end = PyInt_FromLong((long)j);
|
||||
if (!end) {
|
||||
Py_DECREF(start);
|
||||
return NULL;
|
||||
}
|
||||
res = PySlice_New(start, end, NULL);
|
||||
Py_DECREF(start);
|
||||
Py_DECREF(end);
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
static PyObject *
|
||||
instance_slice(PyInstanceObject *inst, int i, int j)
|
||||
{
|
||||
|
@ -980,9 +1001,19 @@ instance_slice(PyInstanceObject *inst, int i, int j)
|
|||
if (getslicestr == NULL)
|
||||
getslicestr = PyString_InternFromString("__getslice__");
|
||||
func = instance_getattr(inst, getslicestr);
|
||||
if (func == NULL)
|
||||
return NULL;
|
||||
arg = Py_BuildValue("(ii)", i, j);
|
||||
|
||||
if (func == NULL) {
|
||||
PyErr_Clear();
|
||||
|
||||
if (getitemstr == NULL)
|
||||
getitemstr = PyString_InternFromString("__getitem__");
|
||||
func = instance_getattr(inst, getitemstr);
|
||||
if (func == NULL)
|
||||
return NULL;
|
||||
arg = Py_BuildValue("(N)", sliceobj_from_intint(i, j));
|
||||
} else
|
||||
arg = Py_BuildValue("(ii)", i, j);
|
||||
|
||||
if (arg == NULL) {
|
||||
Py_DECREF(func);
|
||||
return NULL;
|
||||
|
@ -1038,19 +1069,39 @@ instance_ass_slice(PyInstanceObject *inst, int i, int j, PyObject *value)
|
|||
delslicestr =
|
||||
PyString_InternFromString("__delslice__");
|
||||
func = instance_getattr(inst, delslicestr);
|
||||
if (func == NULL) {
|
||||
PyErr_Clear();
|
||||
if (delitemstr == NULL)
|
||||
delitemstr =
|
||||
PyString_InternFromString("__delitem__");
|
||||
func = instance_getattr(inst, delitemstr);
|
||||
if (func == NULL)
|
||||
return -1;
|
||||
|
||||
arg = Py_BuildValue("(N)",
|
||||
sliceobj_from_intint(i, j));
|
||||
} else
|
||||
arg = Py_BuildValue("(ii)", i, j);
|
||||
}
|
||||
else {
|
||||
if (setslicestr == NULL)
|
||||
setslicestr =
|
||||
PyString_InternFromString("__setslice__");
|
||||
func = instance_getattr(inst, setslicestr);
|
||||
if (func == NULL) {
|
||||
PyErr_Clear();
|
||||
if (setitemstr == NULL)
|
||||
setitemstr =
|
||||
PyString_InternFromString("__setitem__");
|
||||
func = instance_getattr(inst, setitemstr);
|
||||
if (func == NULL)
|
||||
return -1;
|
||||
|
||||
arg = Py_BuildValue("(NO)",
|
||||
sliceobj_from_intint(i, j), value);
|
||||
} else
|
||||
arg = Py_BuildValue("(iiO)", i, j, value);
|
||||
}
|
||||
if (func == NULL)
|
||||
return -1;
|
||||
if (value == NULL)
|
||||
arg = Py_BuildValue("(ii)", i, j);
|
||||
else
|
||||
arg = Py_BuildValue("(iiO)", i, j, value);
|
||||
if (arg == NULL) {
|
||||
Py_DECREF(func);
|
||||
return -1;
|
||||
|
|
Loading…
Reference in New Issue