Apply SF patch #101029: call __getitem__ with a proper slice object if there
authorThomas Wouters <thomas@python.org>
Thu, 17 Aug 2000 22:37:32 +0000 (22:37 +0000)
committerThomas Wouters <thomas@python.org>
Thu, 17 Aug 2000 22:37:32 +0000 (22:37 +0000)
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
Lib/test/output/test_class [new file with mode: 0644]
Lib/test/test_class.py [new file with mode: 0644]
Objects/abstract.c
Objects/classobject.c

index cdf5e62738e496ee9e2b591df1dd272a64991c4d..98625a9966ab472e860374ac1f59401f5272a170 100644 (file)
@@ -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 (file)
index 0000000..93827f1
--- /dev/null
@@ -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 (file)
index 0000000..1fc9971
--- /dev/null
@@ -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
+
index 4a64daa7ce6126a8171be3469d2d49193468e058..8044d86218eb45e092665ca236713ddaa2a40f7b 100644 (file)
@@ -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;
 }
index 3b97a0258d74eecce229166c39d7af92fb6baeda..66d108011f22cb35744cc5d4d5c1a64446b4342a 100644 (file)
@@ -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;