From 1d75a79c009e500923128716a02efbe86135e64e Mon Sep 17 00:00:00 2001
From: Thomas Wouters <thomas@python.org>
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

