Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 6912d4d

Browse files
committed
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.
1 parent f4848da commit 6912d4d

6 files changed

Lines changed: 89 additions & 49 deletions

File tree

Include/abstract.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -911,7 +911,7 @@ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx*/
911911
tuple or list. Use PySequence_Fast_GET_ITEM to access the
912912
members of this list.
913913
914-
Returns NULL on failure. If the object is not a sequence,
914+
Returns NULL on failure. If the object does not support iteration,
915915
raises a TypeError exception with m as the message text.
916916
*/
917917

Lib/test/output/test_coercion

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -516,7 +516,7 @@ test_coercion
516516
[1] % None ... exceptions.TypeError
517517
[1] %= None ... exceptions.TypeError
518518
[1] + <MethodNumber 1> ... exceptions.TypeError
519-
[1] += <MethodNumber 1> ... exceptions.AttributeError
519+
[1] += <MethodNumber 1> ... exceptions.TypeError
520520
[1] - <MethodNumber 1> ... exceptions.TypeError
521521
[1] -= <MethodNumber 1> ... exceptions.TypeError
522522
[1] * <MethodNumber 1> = [1]
@@ -528,7 +528,7 @@ test_coercion
528528
[1] % <MethodNumber 1> ... exceptions.TypeError
529529
[1] %= <MethodNumber 1> ... exceptions.TypeError
530530
[1] + <CoerceNumber 2> ... exceptions.TypeError
531-
[1] += <CoerceNumber 2> ... exceptions.AttributeError
531+
[1] += <CoerceNumber 2> ... exceptions.TypeError
532532
[1] - <CoerceNumber 2> ... exceptions.TypeError
533533
[1] -= <CoerceNumber 2> ... exceptions.TypeError
534534
[1] * <CoerceNumber 2> = [1, 1]

Lib/test/test_extcall.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,20 +58,20 @@ def h(j=1, a=2, h=3):
5858
class Nothing: pass
5959
try:
6060
g(*Nothing())
61-
except AttributeError, attr:
61+
except TypeError, attr:
6262
pass
6363
else:
64-
print "should raise AttributeError: __len__"
64+
print "should raise TypeError"
6565

6666
class Nothing:
6767
def __len__(self):
6868
return 5
6969
try:
7070
g(*Nothing())
71-
except AttributeError, attr:
71+
except TypeError, attr:
7272
pass
7373
else:
74-
print "should raise AttributeError: __getitem__"
74+
print "should raise TypeError"
7575

7676
class Nothing:
7777
def __len__(self):

Lib/test/test_iter.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,39 @@ def test_builtin_list(self):
275275
except OSError:
276276
pass
277277

278+
# Test tuples()'s use of iterators.
279+
def test_builtin_tuple(self):
280+
self.assertEqual(tuple(SequenceClass(5)), (0, 1, 2, 3, 4))
281+
self.assertEqual(tuple(SequenceClass(0)), ())
282+
self.assertEqual(tuple([]), ())
283+
self.assertEqual(tuple(()), ())
284+
self.assertEqual(tuple("abc"), ("a", "b", "c"))
285+
286+
d = {"one": 1, "two": 2, "three": 3}
287+
self.assertEqual(tuple(d), tuple(d.keys()))
288+
289+
self.assertRaises(TypeError, tuple, list)
290+
self.assertRaises(TypeError, tuple, 42)
291+
292+
f = open(TESTFN, "w")
293+
try:
294+
for i in range(5):
295+
f.write("%d\n" % i)
296+
finally:
297+
f.close()
298+
f = open(TESTFN, "r")
299+
try:
300+
self.assertEqual(tuple(f), ("0\n", "1\n", "2\n", "3\n", "4\n"))
301+
f.seek(0, 0)
302+
self.assertEqual(tuple(f.xreadlines()),
303+
("0\n", "1\n", "2\n", "3\n", "4\n"))
304+
finally:
305+
f.close()
306+
try:
307+
unlink(TESTFN)
308+
except OSError:
309+
pass
310+
278311
# Test filter()'s use of iterators.
279312
def test_builtin_filter(self):
280313
self.assertEqual(filter(None, SequenceClass(5)), range(1, 5))

Misc/NEWS

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ Core
2424
min()
2525
reduce()
2626
XXX TODO string.join(), unicode.join()
27-
XXX TODO tuple()
27+
tuple()
2828
XXX TODO zip()
29-
XXX TODO 'x in y' (!) (?)
29+
XXX TODO 'x in y'
3030

3131
What's New in Python 2.1 (final)?
3232
=================================

Objects/abstract.c

Lines changed: 47 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1176,61 +1176,68 @@ PySequence_DelSlice(PyObject *s, int i1, int i2)
11761176
PyObject *
11771177
PySequence_Tuple(PyObject *v)
11781178
{
1179-
PySequenceMethods *m;
1179+
PyObject *it; /* iter(v) */
1180+
int n; /* guess for result tuple size */
1181+
PyObject *result;
1182+
int j;
11801183

11811184
if (v == NULL)
11821185
return null_error();
11831186

1187+
/* Special-case the common tuple and list cases, for efficiency. */
11841188
if (PyTuple_Check(v)) {
11851189
Py_INCREF(v);
11861190
return v;
11871191
}
1188-
11891192
if (PyList_Check(v))
11901193
return PyList_AsTuple(v);
11911194

1192-
/* There used to be code for strings here, but tuplifying strings is
1193-
not a common activity, so I nuked it. Down with code bloat! */
1195+
/* Get iterator. */
1196+
it = PyObject_GetIter(v);
1197+
if (it == NULL)
1198+
return type_error("tuple() argument must support iteration");
11941199

1195-
/* Generic sequence object */
1196-
m = v->ob_type->tp_as_sequence;
1197-
if (m && m->sq_item) {
1198-
int i;
1199-
PyObject *t;
1200-
int n = PySequence_Size(v);
1201-
if (n < 0)
1202-
return NULL;
1203-
t = PyTuple_New(n);
1204-
if (t == NULL)
1205-
return NULL;
1206-
for (i = 0; ; i++) {
1207-
PyObject *item = (*m->sq_item)(v, i);
1208-
if (item == NULL) {
1209-
if (PyErr_ExceptionMatches(PyExc_IndexError))
1210-
PyErr_Clear();
1211-
else {
1212-
Py_DECREF(t);
1213-
t = NULL;
1214-
}
1215-
break;
1216-
}
1217-
if (i >= n) {
1218-
if (n < 500)
1219-
n += 10;
1220-
else
1221-
n += 100;
1222-
if (_PyTuple_Resize(&t, n, 0) != 0)
1223-
break;
1224-
}
1225-
PyTuple_SET_ITEM(t, i, item);
1200+
/* Guess result size and allocate space. */
1201+
n = PySequence_Size(v);
1202+
if (n < 0) {
1203+
PyErr_Clear();
1204+
n = 10; /* arbitrary */
1205+
}
1206+
result = PyTuple_New(n);
1207+
if (result == NULL)
1208+
goto Fail;
1209+
1210+
/* Fill the tuple. */
1211+
for (j = 0; ; ++j) {
1212+
PyObject *item = PyIter_Next(it);
1213+
if (item == NULL) {
1214+
if (PyErr_Occurred())
1215+
goto Fail;
1216+
break;
1217+
}
1218+
if (j >= n) {
1219+
if (n < 500)
1220+
n += 10;
1221+
else
1222+
n += 100;
1223+
if (_PyTuple_Resize(&result, n, 0) != 0)
1224+
goto Fail;
12261225
}
1227-
if (i < n && t != NULL)
1228-
_PyTuple_Resize(&t, i, 0);
1229-
return t;
1226+
PyTuple_SET_ITEM(result, j, item);
12301227
}
12311228

1232-
/* None of the above */
1233-
return type_error("tuple() argument must be a sequence");
1229+
/* Cut tuple back if guess was too large. */
1230+
if (j < n &&
1231+
_PyTuple_Resize(&result, j, 0) != 0)
1232+
goto Fail;
1233+
1234+
Py_DECREF(it);
1235+
return result;
1236+
1237+
Fail:
1238+
Py_XDECREF(result);
1239+
Py_DECREF(it);
1240+
return NULL;
12341241
}
12351242

12361243
PyObject *

0 commit comments

Comments
 (0)