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

@ -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 (i < n && t != NULL)
_PyTuple_Resize(&t, i, 0);
return t;
if (j >= n) {
if (n < 500)
n += 10;
else
n += 100;
if (_PyTuple_Resize(&result, n, 0) != 0)
goto Fail;
}
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 *