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:
parent
f4848dac41
commit
6912d4ddf0
|
@ -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.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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)?
|
||||||
=================================
|
=================================
|
||||||
|
|
|
@ -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;
|
|
||||||
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();
|
PyErr_Clear();
|
||||||
else {
|
n = 10; /* arbitrary */
|
||||||
Py_DECREF(t);
|
|
||||||
t = NULL;
|
|
||||||
}
|
}
|
||||||
|
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;
|
break;
|
||||||
}
|
}
|
||||||
if (i >= n) {
|
if (j >= n) {
|
||||||
if (n < 500)
|
if (n < 500)
|
||||||
n += 10;
|
n += 10;
|
||||||
else
|
else
|
||||||
n += 100;
|
n += 100;
|
||||||
if (_PyTuple_Resize(&t, n, 0) != 0)
|
if (_PyTuple_Resize(&result, n, 0) != 0)
|
||||||
break;
|
goto Fail;
|
||||||
}
|
}
|
||||||
PyTuple_SET_ITEM(t, i, item);
|
PyTuple_SET_ITEM(result, j, item);
|
||||||
}
|
|
||||||
if (i < n && t != NULL)
|
|
||||||
_PyTuple_Resize(&t, i, 0);
|
|
||||||
return t;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 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 *
|
||||||
|
|
Loading…
Reference in New Issue