]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
Generalize tuple() to work nicely with iterators.
authorTim Peters <tim.peters@gmail.com>
Sat, 5 May 2001 03:56:37 +0000 (03:56 +0000)
committerTim Peters <tim.peters@gmail.com>
Sat, 5 May 2001 03:56:37 +0000 (03:56 +0000)
NEEDS DOC CHANGES.
This one surprised me!  While I expected tuple() to be a no-brainer, turns
out it's actually dripping with consequences:
1. It will *allow* the popular PySequence_Fast() to work with any iterable
   object (code for that not yet checked in, but should be trivial).
2. It caused two std tests to fail.  This because some places used
   PyTuple_Sequence() (the C spelling of tuple()) as an indirect way to test
   whether something *is* a sequence.  But tuple() code only looked for the
   existence of sq->item to determine that, and e.g. an instance passed
   that test whether or not it supported the other operations tuple()
   needed (e.g., __len__).  So some things the tests *expected* to fail
   with an AttributeError now fail with a TypeError instead.  This looks
   like an improvement to me; e.g., test_coercion used to produce 559
   TypeErrors and 2 AttributeErrors, and now they're all TypeErrors.  The
   error details are more informative too, because the places calling this
   were *looking* for TypeErrors in order to replace the generic tuple()
   "not a sequence" msg with their own more specific text, and
   AttributeErrors snuck by that.

Include/abstract.h
Lib/test/output/test_coercion
Lib/test/test_extcall.py
Lib/test/test_iter.py
Misc/NEWS
Objects/abstract.c

index ac9e568ef0ef5e6f8e4449528f75bbdb84503d67..d5f4a9978d4dcb95a484d97de26d2f5ece64392c 100644 (file)
@@ -911,7 +911,7 @@ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx*/
          tuple or list.  Use PySequence_Fast_GET_ITEM to access the
          members of this list.
 
-         Returns NULL on failure.  If the object is not a sequence,
+         Returns NULL on failure.  If the object does not support iteration,
          raises a TypeError exception with m as the message text.
        */
 
index 8c5f6e0dd962cbfa1e58b4f2654cd9f256d092f9..42097502b38dc275d0ebfdca9f7db88b3b853fbb 100644 (file)
@@ -516,7 +516,7 @@ test_coercion
 [1] % None ... exceptions.TypeError
 [1] %= None ... exceptions.TypeError
 [1] + <MethodNumber 1> ... exceptions.TypeError
-[1] += <MethodNumber 1> ... exceptions.AttributeError
+[1] += <MethodNumber 1> ... exceptions.TypeError
 [1] - <MethodNumber 1> ... exceptions.TypeError
 [1] -= <MethodNumber 1> ... exceptions.TypeError
 [1] * <MethodNumber 1> = [1]
@@ -528,7 +528,7 @@ test_coercion
 [1] % <MethodNumber 1> ... exceptions.TypeError
 [1] %= <MethodNumber 1> ... exceptions.TypeError
 [1] + <CoerceNumber 2> ... exceptions.TypeError
-[1] += <CoerceNumber 2> ... exceptions.AttributeError
+[1] += <CoerceNumber 2> ... exceptions.TypeError
 [1] - <CoerceNumber 2> ... exceptions.TypeError
 [1] -= <CoerceNumber 2> ... exceptions.TypeError
 [1] * <CoerceNumber 2> = [1, 1]
index 472090136d51ce43fb926df95fad0ce1ae4e46b3..274e943ec6347122295254ec87f5915dad0c9926 100644 (file)
@@ -58,20 +58,20 @@ g(1, 2, 3, *(4, 5))
 class Nothing: pass
 try:
     g(*Nothing())
-except AttributeError, attr:
+except TypeError, attr:
     pass
 else:
-    print "should raise AttributeError: __len__"
+    print "should raise TypeError"
 
 class Nothing:
     def __len__(self):
         return 5
 try:
     g(*Nothing())
-except AttributeError, attr:
+except TypeError, attr:
     pass
 else:
-    print "should raise AttributeError: __getitem__"
+    print "should raise TypeError"
 
 class Nothing:
     def __len__(self):
index 55845879a581c62b6aa43994375b0ffc03990805..bfe032fc52c0a757a089e9086b5c40275b97e7c4 100644 (file)
@@ -275,6 +275,39 @@ class TestCase(unittest.TestCase):
             except OSError:
                 pass
 
+    # Test tuples()'s use of iterators.
+    def test_builtin_tuple(self):
+        self.assertEqual(tuple(SequenceClass(5)), (0, 1, 2, 3, 4))
+        self.assertEqual(tuple(SequenceClass(0)), ())
+        self.assertEqual(tuple([]), ())
+        self.assertEqual(tuple(()), ())
+        self.assertEqual(tuple("abc"), ("a", "b", "c"))
+
+        d = {"one": 1, "two": 2, "three": 3}
+        self.assertEqual(tuple(d), tuple(d.keys()))
+
+        self.assertRaises(TypeError, tuple, list)
+        self.assertRaises(TypeError, tuple, 42)
+
+        f = open(TESTFN, "w")
+        try:
+            for i in range(5):
+                f.write("%d\n" % i)
+        finally:
+            f.close()
+        f = open(TESTFN, "r")
+        try:
+            self.assertEqual(tuple(f), ("0\n", "1\n", "2\n", "3\n", "4\n"))
+            f.seek(0, 0)
+            self.assertEqual(tuple(f.xreadlines()),
+                             ("0\n", "1\n", "2\n", "3\n", "4\n"))
+        finally:
+            f.close()
+            try:
+                unlink(TESTFN)
+            except OSError:
+                pass
+
     # Test filter()'s use of iterators.
     def test_builtin_filter(self):
         self.assertEqual(filter(None, SequenceClass(5)), range(1, 5))
index 01455d21cf94570c11a0398f33e0500ba38bfecf..d838c0f690d1fc617752d890a14b225173dbe4ad 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -24,9 +24,9 @@ Core
     min()
     reduce()
     XXX TODO string.join(), unicode.join()
-    XXX TODO tuple()
+    tuple()
     XXX TODO zip()
-    XXX TODO 'x in y' (!) (?)
+    XXX TODO 'x in y'
 
 What's New in Python 2.1 (final)?
 =================================
index 7133867684b9e76633811959701cca010929dba4..2fe6b1f8391d5369d5e84e7d7dd0899f73cd4e12 100644 (file)
@@ -1176,61 +1176,68 @@ PySequence_DelSlice(PyObject *s, int i1, int i2)
 PyObject *
 PySequence_Tuple(PyObject *v)
 {
-       PySequenceMethods *m;
+       PyObject *it;  /* iter(v) */
+       int n;         /* guess for result tuple size */
+       PyObject *result;
+       int j;
 
        if (v == NULL)
                return null_error();
 
+       /* Special-case the common tuple and list cases, for efficiency. */
        if (PyTuple_Check(v)) {
                Py_INCREF(v);
                return v;
        }
-
        if (PyList_Check(v))
                return PyList_AsTuple(v);
 
-       /* There used to be code for strings here, but tuplifying strings is
-          not a common activity, so I nuked it.  Down with code bloat! */
+       /* Get iterator. */
+       it = PyObject_GetIter(v);
+       if (it == NULL)
+               return type_error("tuple() argument must support iteration");
 
-       /* Generic sequence object */
-       m = v->ob_type->tp_as_sequence;
-       if (m && m->sq_item) {
-               int i;
-               PyObject *t;
-               int n = PySequence_Size(v);
-               if (n < 0)
-                       return NULL;
-               t = PyTuple_New(n);
-               if (t == NULL)
-                       return NULL;
-               for (i = 0; ; i++) {
-                       PyObject *item = (*m->sq_item)(v, i);
-                       if (item == NULL) {
-                               if (PyErr_ExceptionMatches(PyExc_IndexError))
-                                       PyErr_Clear();
-                               else {
-                                       Py_DECREF(t);
-                                       t = NULL;
-                               }
-                               break;
-                       }
-                       if (i >= n) {
-                               if (n < 500)
-                                       n += 10;
-                               else
-                                       n += 100;
-                               if (_PyTuple_Resize(&t, n, 0) != 0)
-                                       break;
-                       }
-                       PyTuple_SET_ITEM(t, i, item);
+       /* Guess result size and allocate space. */
+       n = PySequence_Size(v);
+       if (n < 0) {
+               PyErr_Clear();
+               n = 10;  /* arbitrary */
+       }
+       result = PyTuple_New(n);
+       if (result == NULL)
+               goto Fail;
+
+       /* Fill the tuple. */
+       for (j = 0; ; ++j) {
+               PyObject *item = PyIter_Next(it);
+               if (item == NULL) {
+                       if (PyErr_Occurred())
+                               goto Fail;
+                       break;
+               }
+               if (j >= n) {
+                       if (n < 500)
+                               n += 10;
+                       else
+                               n += 100;
+                       if (_PyTuple_Resize(&result, n, 0) != 0)
+                               goto Fail;
                }
-               if (i < n && t != NULL)
-                       _PyTuple_Resize(&t, i, 0);
-               return t;
+               PyTuple_SET_ITEM(result, j, item);
        }
 
-       /* None of the above */
-       return type_error("tuple() argument must be a sequence");
+       /* Cut tuple back if guess was too large. */
+       if (j < n &&
+           _PyTuple_Resize(&result, j, 0) != 0)
+               goto Fail;
+
+       Py_DECREF(it);
+       return result;
+
+Fail:
+       Py_XDECREF(result);
+       Py_DECREF(it);
+       return NULL;
 }
 
 PyObject *