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.
This commit is contained in:
Tim Peters 2001-05-05 03:56:37 +00:00
parent f4848dac41
commit 6912d4ddf0
6 changed files with 89 additions and 49 deletions

View File

@ -911,7 +911,7 @@ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx*/
tuple or list. Use PySequence_Fast_GET_ITEM to access the tuple or list. Use PySequence_Fast_GET_ITEM to access the
members of this list. 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. raises a TypeError exception with m as the message text.
*/ */

View File

@ -516,7 +516,7 @@ test_coercion
[1] % None ... exceptions.TypeError [1] % None ... exceptions.TypeError
[1] %= None ... exceptions.TypeError [1] %= None ... exceptions.TypeError
[1] + <MethodNumber 1> ... 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> ... exceptions.TypeError [1] -= <MethodNumber 1> ... exceptions.TypeError
[1] * <MethodNumber 1> = [1] [1] * <MethodNumber 1> = [1]
@ -528,7 +528,7 @@ test_coercion
[1] % <MethodNumber 1> ... exceptions.TypeError [1] % <MethodNumber 1> ... exceptions.TypeError
[1] %= <MethodNumber 1> ... exceptions.TypeError [1] %= <MethodNumber 1> ... exceptions.TypeError
[1] + <CoerceNumber 2> ... 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> ... exceptions.TypeError [1] -= <CoerceNumber 2> ... exceptions.TypeError
[1] * <CoerceNumber 2> = [1, 1] [1] * <CoerceNumber 2> = [1, 1]

View File

@ -58,20 +58,20 @@ g(1, 2, 3, *(4, 5))
class Nothing: pass class Nothing: pass
try: try:
g(*Nothing()) g(*Nothing())
except AttributeError, attr: except TypeError, attr:
pass pass
else: else:
print "should raise AttributeError: __len__" print "should raise TypeError"
class Nothing: class Nothing:
def __len__(self): def __len__(self):
return 5 return 5
try: try:
g(*Nothing()) g(*Nothing())
except AttributeError, attr: except TypeError, attr:
pass pass
else: else:
print "should raise AttributeError: __getitem__" print "should raise TypeError"
class Nothing: class Nothing:
def __len__(self): def __len__(self):

View File

@ -275,6 +275,39 @@ class TestCase(unittest.TestCase):
except OSError: except OSError:
pass 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. # Test filter()'s use of iterators.
def test_builtin_filter(self): def test_builtin_filter(self):
self.assertEqual(filter(None, SequenceClass(5)), range(1, 5)) self.assertEqual(filter(None, SequenceClass(5)), range(1, 5))

View File

@ -24,9 +24,9 @@ Core
min() min()
reduce() reduce()
XXX TODO string.join(), unicode.join() XXX TODO string.join(), unicode.join()
XXX TODO tuple() tuple()
XXX TODO zip() XXX TODO zip()
XXX TODO 'x in y' (!) (?) XXX TODO 'x in y'
What's New in Python 2.1 (final)? What's New in Python 2.1 (final)?
================================= =================================

View File

@ -1176,61 +1176,68 @@ PySequence_DelSlice(PyObject *s, int i1, int i2)
PyObject * PyObject *
PySequence_Tuple(PyObject *v) PySequence_Tuple(PyObject *v)
{ {
PySequenceMethods *m; PyObject *it; /* iter(v) */
int n; /* guess for result tuple size */
PyObject *result;
int j;
if (v == NULL) if (v == NULL)
return null_error(); return null_error();
/* Special-case the common tuple and list cases, for efficiency. */
if (PyTuple_Check(v)) { if (PyTuple_Check(v)) {
Py_INCREF(v); Py_INCREF(v);
return v; return v;
} }
if (PyList_Check(v)) if (PyList_Check(v))
return PyList_AsTuple(v); return PyList_AsTuple(v);
/* There used to be code for strings here, but tuplifying strings is /* Get iterator. */
not a common activity, so I nuked it. Down with code bloat! */ it = PyObject_GetIter(v);
if (it == NULL)
return type_error("tuple() argument must support iteration");
/* Generic sequence object */ /* Guess result size and allocate space. */
m = v->ob_type->tp_as_sequence; n = PySequence_Size(v);
if (m && m->sq_item) { if (n < 0) {
int i; PyErr_Clear();
PyObject *t; n = 10; /* arbitrary */
int n = PySequence_Size(v); }
if (n < 0) result = PyTuple_New(n);
return NULL; if (result == NULL)
t = PyTuple_New(n); goto Fail;
if (t == NULL)
return NULL; /* Fill the tuple. */
for (i = 0; ; i++) { for (j = 0; ; ++j) {
PyObject *item = (*m->sq_item)(v, i); PyObject *item = PyIter_Next(it);
if (item == NULL) { if (item == NULL) {
if (PyErr_ExceptionMatches(PyExc_IndexError)) if (PyErr_Occurred())
PyErr_Clear(); goto Fail;
else { break;
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);
} }
if (i < n && t != NULL) if (j >= n) {
_PyTuple_Resize(&t, i, 0); if (n < 500)
return t; n += 10;
else
n += 100;
if (_PyTuple_Resize(&result, n, 0) != 0)
goto Fail;
}
PyTuple_SET_ITEM(result, j, item);
} }
/* None of the above */ /* Cut tuple back if guess was too large. */
return type_error("tuple() argument must be a sequence"); 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 * PyObject *