From 1d75a79c009e500923128716a02efbe86135e64e Mon Sep 17 00:00:00 2001 From: Thomas Wouters Date: Thu, 17 Aug 2000 22:37:32 +0000 Subject: 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. --- Doc/ref/ref3.tex | 27 ++++-- Lib/test/output/test_class | 101 +++++++++++++++++++++ Lib/test/test_class.py | 219 +++++++++++++++++++++++++++++++++++++++++++++ Objects/abstract.c | 37 ++++++++ Objects/classobject.c | 69 ++++++++++++-- 5 files changed, 435 insertions(+), 18 deletions(-) create mode 100644 Lib/test/output/test_class create mode 100644 Lib/test/test_class.py diff --git a/Doc/ref/ref3.tex b/Doc/ref/ref3.tex index cdf5e62..98625a9 100644 --- a/Doc/ref/ref3.tex +++ b/Doc/ref/ref3.tex @@ -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}} diff --git a/Lib/test/output/test_class b/Lib/test/output/test_class new file mode 100644 index 0000000..93827f1 --- /dev/null +++ b/Lib/test/output/test_class @@ -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',) diff --git a/Lib/test/test_class.py b/Lib/test/test_class.py new file mode 100644 index 0000000..1fc9971 --- /dev/null +++ b/Lib/test/test_class.py @@ -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 + diff --git a/Objects/abstract.c b/Objects/abstract.c index 4a64daa..8044d86 100644 --- a/Objects/abstract.c +++ b/Objects/abstract.c @@ -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; } diff --git a/Objects/classobject.c b/Objects/classobject.c index 3b97a02..66d1080 100644 --- a/Objects/classobject.c +++ b/Objects/classobject.c @@ -972,6 +972,27 @@ instance_item(PyInstanceObject *inst, int i) } 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) { PyObject *func, *arg, *res; @@ -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; -- cgit v0.12