]> granicus.if.org Git - python/commitdiff
Issue 23704: Add index(), copy(), and insert() to deques. Register deques as a...
authorRaymond Hettinger <python@rcn.com>
Sat, 21 Mar 2015 08:37:37 +0000 (01:37 -0700)
committerRaymond Hettinger <python@rcn.com>
Sat, 21 Mar 2015 08:37:37 +0000 (01:37 -0700)
Doc/library/collections.rst
Lib/collections/__init__.py
Lib/test/test_collections.py
Lib/test/test_deque.py
Misc/NEWS
Modules/_collectionsmodule.c

index c21f4563b17b6f2f0dfdde07e14dae467f194384..2b4e4ba1ec5b541a915258198a758da5ea0b5c2b 100644 (file)
@@ -437,6 +437,13 @@ or subtracting from an empty counter.
         Remove all elements from the deque leaving it with length 0.
 
 
+    .. method:: copy()
+
+        Create a shallow copy of the deque.
+
+        .. versionadded:: 3.5
+
+
     .. method:: count(x)
 
         Count the number of deque elements equal to *x*.
@@ -457,6 +464,21 @@ or subtracting from an empty counter.
         elements in the iterable argument.
 
 
+    .. method:: index(x[, start[, end]])
+
+        Return the position of *x* in the deque.  Returns the first match
+        or raises :exc:`ValueError` if not found.
+
+        .. versionadded:: 3.5
+
+
+    .. method:: insert(i, x)
+
+        Insert *x* into the deque at position *i*.
+
+        .. versionadded:: 3.5
+
+
     .. method:: pop()
 
         Remove and return an element from the right side of the deque. If no
index ce67f0b31250a4fe7a9171c4dd3840148985bbde..9fd3532158c876e706fc097a60534006ea9e49ab 100644 (file)
@@ -16,6 +16,8 @@ from _weakref import proxy as _proxy
 from itertools import repeat as _repeat, chain as _chain, starmap as _starmap
 from reprlib import recursive_repr as _recursive_repr
 
+MutableSequence.register(deque)
+
 ################################################################################
 ### OrderedDict
 ################################################################################
index a94b5da860f75d12eeb2612e812a296a159aa82a..958fb6282453fe1adb8c809a11d6c033ea06c11d 100644 (file)
@@ -13,6 +13,7 @@ import re
 import sys
 from collections import UserDict
 from collections import ChainMap
+from collections import deque
 from collections.abc import Hashable, Iterable, Iterator
 from collections.abc import Sized, Container, Callable
 from collections.abc import Set, MutableSet
@@ -1014,7 +1015,7 @@ class TestCollectionABCs(ABCTestCase):
         for sample in [tuple, str, bytes]:
             self.assertNotIsInstance(sample(), MutableSequence)
             self.assertFalse(issubclass(sample, MutableSequence))
-        for sample in [list, bytearray]:
+        for sample in [list, bytearray, deque]:
             self.assertIsInstance(sample(), MutableSequence)
             self.assertTrue(issubclass(sample, MutableSequence))
         self.assertFalse(issubclass(str, MutableSequence))
index 7d694485852fa61840783cbeb1331410af602d27..07e5ef98eb5d8cb9b5aaa3d151bdea99e53ec5bb 100644 (file)
@@ -231,6 +231,54 @@ class TestBasic(unittest.TestCase):
         self.assertRaises(IndexError, d.__getitem__, 0)
         self.assertRaises(IndexError, d.__getitem__, -1)
 
+    def test_index(self):
+        for n in 1, 2, 30, 40, 200:
+
+            d = deque(range(n))
+            for i in range(n):
+                self.assertEqual(d.index(i), i)
+
+            with self.assertRaises(ValueError):
+                d.index(n+1)
+
+            # Test detection of mutation during iteration
+            d = deque(range(n))
+            d[n//2] = MutateCmp(d, False)
+            with self.assertRaises(RuntimeError):
+                d.index(n)
+
+            # Test detection of comparison exceptions
+            d = deque(range(n))
+            d[n//2] = BadCmp()
+            with self.assertRaises(RuntimeError):
+                d.index(n)
+
+        # Test start and stop arguments behavior matches list.index()
+        elements = 'ABCDEFGHI'
+        nonelement = 'Z'
+        d = deque(elements * 2)
+        s = list(elements * 2)
+        for start in range(-5 - len(s)*2, 5 + len(s) * 2):
+            for stop in range(-5 - len(s)*2, 5 + len(s) * 2):
+                for element in elements + 'Z':
+                    try:
+                        target = s.index(element, start, stop)
+                    except ValueError:
+                        with self.assertRaises(ValueError):
+                            d.index(element, start, stop)
+                    else:
+                        self.assertEqual(d.index(element, start, stop), target)
+
+    def test_insert(self):
+        # Test to make sure insert behaves like lists
+        elements = 'ABCDEFGHI'
+        for i in range(-5 - len(elements)*2, 5 + len(elements) * 2):
+            d = deque('ABCDEFGHI')
+            s = list('ABCDEFGHI')
+            d.insert(i, 'Z')
+            s.insert(i, 'Z')
+            self.assertEqual(list(d), s)
+
     def test_setitem(self):
         n = 200
         d = deque(range(n))
@@ -524,6 +572,15 @@ class TestBasic(unittest.TestCase):
         self.assertNotEqual(id(d), id(e))
         self.assertEqual(list(d), list(e))
 
+    def test_copy_method(self):
+        mut = [10]
+        d = deque([mut])
+        e = d.copy()
+        self.assertEqual(list(d), list(e))
+        mut[0] = 11
+        self.assertNotEqual(id(d), id(e))
+        self.assertEqual(list(d), list(e))
+
     def test_reversed(self):
         for s in ('abcd', range(2000)):
             self.assertEqual(list(reversed(deque(s))), list(reversed(s)))
index 5053fc59f938504dca3e49c7580c712dd31ea6ce..cc8e20ad2cab271b2aff72b1bf180d25d5ab7013 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -27,6 +27,10 @@ Library
   and socket open until the garbage collector cleans them up.  Patch by
   Martin Panter.
 
+- Issue #23704: collections.deque() objects now support methods for index(),
+  insert(), and copy().  This allows deques to be registered as a
+  MutableSequence and it improves their substitutablity for lists.
+
 - Issue #23715: :func:`signal.sigwaitinfo` and :func:`signal.sigtimedwait` are
   now retried when interrupted by a signal not in the *sigset* parameter, if
   the signal handler does not raise an exception. signal.sigtimedwait()
index 26a0c8ff6c860adf7a8b15a3a1ff1539b81d1b10..223ad531935843284474685aaadd60e8ea45ab71 100644 (file)
@@ -762,6 +762,91 @@ deque_len(dequeobject *deque)
     return Py_SIZE(deque);
 }
 
+static PyObject *
+deque_index(dequeobject *deque, PyObject *args)
+{
+    Py_ssize_t i, start=0, stop=Py_SIZE(deque);
+    PyObject *v, *item;
+    block *b = deque->leftblock;
+    Py_ssize_t index = deque->leftindex;
+    size_t start_state = deque->state;
+
+    if (!PyArg_ParseTuple(args, "O|O&O&:index", &v,
+                                _PyEval_SliceIndex, &start,
+                                _PyEval_SliceIndex, &stop))
+        return NULL;
+    if (start < 0) {
+        start += Py_SIZE(deque);
+        if (start < 0)
+            start = 0;
+    }
+    if (stop < 0) {
+        stop += Py_SIZE(deque);
+        if (stop < 0)
+            stop = 0;
+    }
+
+    for (i=0 ; i<stop ; i++) {
+        if (i >= start) {
+            int cmp;
+            CHECK_NOT_END(b);
+            item = b->data[index];
+            cmp = PyObject_RichCompareBool(item, v, Py_EQ);
+            if (cmp > 0)
+                return PyLong_FromSsize_t(i);
+            else if (cmp < 0)
+                return NULL;
+            if (start_state != deque->state) {
+                PyErr_SetString(PyExc_RuntimeError,
+                                "deque mutated during iteration");
+                return NULL;
+            }
+        }
+        index++;
+        if (index == BLOCKLEN) {
+            b = b->rightlink;
+            index = 0;
+        }
+    }
+    PyErr_Format(PyExc_ValueError, "%R is not in deque", v);
+    return NULL;
+}
+
+PyDoc_STRVAR(index_doc,
+"D.index(value, [start, [stop]]) -> integer -- return first index of value.\n"
+"Raises ValueError if the value is not present.");
+
+static PyObject *
+deque_insert(dequeobject *deque, PyObject *args)
+{
+    Py_ssize_t index;
+    Py_ssize_t n = Py_SIZE(deque);
+    PyObject *value;
+    PyObject *rv;
+
+    if (!PyArg_ParseTuple(args, "nO:insert", &index, &value))
+        return NULL;
+    if (index >= n)
+        return deque_append(deque, value);
+    if (index <= -n || index == 0)
+        return deque_appendleft(deque, value);
+    if (_deque_rotate(deque, -index))
+        return NULL;
+    if (index < 0)
+        rv = deque_append(deque, value);
+    else
+        rv = deque_appendleft(deque, value);
+    if (rv == NULL)
+        return NULL;
+    Py_DECREF(rv);
+    if (_deque_rotate(deque, index))
+        return NULL;
+    Py_RETURN_NONE;
+}
+
+PyDoc_STRVAR(insert_doc,
+"D.insert(index, object) -- insert object before index");
+
 static PyObject *
 deque_remove(dequeobject *deque, PyObject *value)
 {
@@ -1208,12 +1293,18 @@ static PyMethodDef deque_methods[] = {
         METH_NOARGS,             clear_doc},
     {"__copy__",                (PyCFunction)deque_copy,
         METH_NOARGS,             copy_doc},
+    {"copy",                    (PyCFunction)deque_copy,
+        METH_NOARGS,             copy_doc},
     {"count",                   (PyCFunction)deque_count,
         METH_O,                         count_doc},
     {"extend",                  (PyCFunction)deque_extend,
         METH_O,                  extend_doc},
     {"extendleft",              (PyCFunction)deque_extendleft,
         METH_O,                  extendleft_doc},
+    {"index",                   (PyCFunction)deque_index,
+        METH_VARARGS,            index_doc},
+    {"insert",                  (PyCFunction)deque_insert,
+        METH_VARARGS,            insert_doc},
     {"pop",                     (PyCFunction)deque_pop,
         METH_NOARGS,             pop_doc},
     {"popleft",                 (PyCFunction)deque_popleft,