summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorThomas Wouters <thomas@python.org>2000-08-17 22:37:32 (GMT)
committerThomas Wouters <thomas@python.org>2000-08-17 22:37:32 (GMT)
commit1d75a79c009e500923128716a02efbe86135e64e (patch)
tree9c978062db6b1e9c4c775a9d4b763fd115bd671e
parent68add2e9389e9cb2b74e448201ec101564391fe5 (diff)
downloadcpython-1d75a79c009e500923128716a02efbe86135e64e.zip
cpython-1d75a79c009e500923128716a02efbe86135e64e.tar.gz
cpython-1d75a79c009e500923128716a02efbe86135e64e.tar.bz2
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.
-rw-r--r--Doc/ref/ref3.tex27
-rw-r--r--Lib/test/output/test_class101
-rw-r--r--Lib/test/test_class.py219
-rw-r--r--Objects/abstract.c37
-rw-r--r--Objects/classobject.c69
5 files changed, 435 insertions, 18 deletions
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;