summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTim Peters <tim.peters@gmail.com>2001-05-05 03:56:37 (GMT)
committerTim Peters <tim.peters@gmail.com>2001-05-05 03:56:37 (GMT)
commit6912d4ddf0504a3d5611ddd12cbde3354bd48279 (patch)
treeaf9162f00d5169fc8e9c13c0046d61051c2463ed
parentf4848dac41689d1f2f8bd224bd935beae9b8df86 (diff)
downloadcpython-6912d4ddf0504a3d5611ddd12cbde3354bd48279.zip
cpython-6912d4ddf0504a3d5611ddd12cbde3354bd48279.tar.gz
cpython-6912d4ddf0504a3d5611ddd12cbde3354bd48279.tar.bz2
Generalize tuple() to work nicely with iterators.
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.
-rw-r--r--Include/abstract.h2
-rw-r--r--Lib/test/output/test_coercion4
-rw-r--r--Lib/test/test_extcall.py8
-rw-r--r--Lib/test/test_iter.py33
-rw-r--r--Misc/NEWS4
-rw-r--r--Objects/abstract.c87
6 files changed, 89 insertions, 49 deletions
diff --git a/Include/abstract.h b/Include/abstract.h
index ac9e568..d5f4a99 100644
--- a/Include/abstract.h
+++ b/Include/abstract.h
@@ -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.
*/
diff --git a/Lib/test/output/test_coercion b/Lib/test/output/test_coercion
index 8c5f6e0..4209750 100644
--- a/Lib/test/output/test_coercion
+++ b/Lib/test/output/test_coercion
@@ -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]
diff --git a/Lib/test/test_extcall.py b/Lib/test/test_extcall.py
index 4720901..274e943 100644
--- a/Lib/test/test_extcall.py
+++ b/Lib/test/test_extcall.py
@@ -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):
diff --git a/Lib/test/test_iter.py b/Lib/test/test_iter.py
index 5584587..bfe032f 100644
--- a/Lib/test/test_iter.py
+++ b/Lib/test/test_iter.py
@@ -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))
diff --git a/Misc/NEWS b/Misc/NEWS
index 01455d2..d838c0f 100644
--- 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)?
=================================
diff --git a/Objects/abstract.c b/Objects/abstract.c
index 7133867..2fe6b1f 100644
--- a/Objects/abstract.c
+++ b/Objects/abstract.c
@@ -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 *