Skip to content

Commit 6b27cda

Browse files
committed
Convert iterator __len__() methods to a private API.
1 parent 9ceebd5 commit 6b27cda

File tree

16 files changed

+169
-94
lines changed

16 files changed

+169
-94
lines changed

Include/abstract.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,21 @@ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx*/
422422
PyAPI_FUNC(int) PyObject_Length(PyObject *o);
423423
#define PyObject_Length PyObject_Size
424424

425+
PyAPI_FUNC(int) _PyObject_LengthCue(PyObject *o);
426+
427+
/*
428+
Return the size of object o. If the object, o, provides
429+
both sequence and mapping protocols, the sequence size is
430+
returned. On error, -1 is returned. If the object provides
431+
a _length_cue() method, its value is returned. This is the
432+
equivalent to the Python expression:
433+
try:
434+
return len(o)
435+
except (AttributeError, TypeError):
436+
if hasattr(o, '_length_cue'):
437+
return o._length_cue()
438+
raise
439+
*/
425440

426441
PyAPI_FUNC(PyObject *) PyObject_GetItem(PyObject *o, PyObject *key);
427442

Lib/test/test_enumerate.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ def test_xrange_optimization(self):
144144

145145
def test_len(self):
146146
# This is an implementation detail, not an interface requirement
147+
from test.test_iterlen import len
147148
for s in ('hello', tuple('hello'), list('hello'), xrange(5)):
148149
self.assertEqual(len(reversed(s)), len(s))
149150
r = reversed(s)

Lib/test/test_iterlen.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,22 @@
4343

4444
import unittest
4545
from test import test_support
46-
from itertools import repeat, count
46+
from itertools import repeat
4747
from collections import deque
4848
from UserList import UserList
49+
from __builtin__ import len as _len
4950

5051
n = 10
5152

53+
def len(obj):
54+
try:
55+
return _len(obj)
56+
except TypeError:
57+
try:
58+
return obj._length_cue()
59+
except AttributeError:
60+
raise TypeError
61+
5262
class TestInvariantWithoutMutations(unittest.TestCase):
5363

5464
def test_invariant(self):

Lib/test/test_itertools.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -670,6 +670,7 @@ def test_tee(self):
670670
class LengthTransparency(unittest.TestCase):
671671

672672
def test_repeat(self):
673+
from test.test_iterlen import len
673674
self.assertEqual(len(repeat(None, 50)), 50)
674675
self.assertRaises(TypeError, len, repeat(None))
675676

Misc/NEWS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ What's New in Python 2.5 alpha 1?
1212
Core and builtins
1313
-----------------
1414

15+
- All iterators now have a Boolean value of true. Formerly, some iterators
16+
supported a __len__() method which evaluated to False when the iterator
17+
was empty.
18+
1519
- On 64-bit platforms, when __len__() returns a value that cannot be
1620
represented as a C int, raise OverflowError.
1721

Modules/collectionsmodule.c

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -935,15 +935,17 @@ dequeiter_next(dequeiterobject *it)
935935
return item;
936936
}
937937

938-
static int
938+
static PyObject *
939939
dequeiter_len(dequeiterobject *it)
940940
{
941-
return it->counter;
941+
return PyInt_FromLong(it->counter);
942942
}
943943

944-
static PySequenceMethods dequeiter_as_sequence = {
945-
(inquiry)dequeiter_len, /* sq_length */
946-
0, /* sq_concat */
944+
PyDoc_STRVAR(length_cue_doc, "Private method returning an estimate of len(list(it)).");
945+
946+
static PyMethodDef dequeiter_methods[] = {
947+
{"_length_cue", (PyCFunction)dequeiter_len, METH_NOARGS, length_cue_doc},
948+
{NULL, NULL} /* sentinel */
947949
};
948950

949951
PyTypeObject dequeiter_type = {
@@ -960,7 +962,7 @@ PyTypeObject dequeiter_type = {
960962
0, /* tp_compare */
961963
0, /* tp_repr */
962964
0, /* tp_as_number */
963-
&dequeiter_as_sequence, /* tp_as_sequence */
965+
0, /* tp_as_sequence */
964966
0, /* tp_as_mapping */
965967
0, /* tp_hash */
966968
0, /* tp_call */
@@ -976,6 +978,7 @@ PyTypeObject dequeiter_type = {
976978
0, /* tp_weaklistoffset */
977979
PyObject_SelfIter, /* tp_iter */
978980
(iternextfunc)dequeiter_next, /* tp_iternext */
981+
dequeiter_methods, /* tp_methods */
979982
0,
980983
};
981984

@@ -1042,7 +1045,7 @@ PyTypeObject dequereviter_type = {
10421045
0, /* tp_compare */
10431046
0, /* tp_repr */
10441047
0, /* tp_as_number */
1045-
&dequeiter_as_sequence, /* tp_as_sequence */
1048+
0, /* tp_as_sequence */
10461049
0, /* tp_as_mapping */
10471050
0, /* tp_hash */
10481051
0, /* tp_call */
@@ -1058,6 +1061,7 @@ PyTypeObject dequereviter_type = {
10581061
0, /* tp_weaklistoffset */
10591062
PyObject_SelfIter, /* tp_iter */
10601063
(iternextfunc)dequereviter_next, /* tp_iternext */
1064+
dequeiter_methods, /* tp_methods */
10611065
0,
10621066
};
10631067

Modules/itertoolsmodule.c

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2336,17 +2336,21 @@ repeat_repr(repeatobject *ro)
23362336
return result;
23372337
}
23382338

2339-
static int
2339+
static PyObject *
23402340
repeat_len(repeatobject *ro)
23412341
{
2342-
if (ro->cnt == -1)
2342+
if (ro->cnt == -1) {
23432343
PyErr_SetString(PyExc_TypeError, "len() of unsized object");
2344-
return (int)(ro->cnt);
2344+
return NULL;
2345+
}
2346+
return PyInt_FromLong(ro->cnt);
23452347
}
23462348

2347-
static PySequenceMethods repeat_as_sequence = {
2348-
(inquiry)repeat_len, /* sq_length */
2349-
0, /* sq_concat */
2349+
PyDoc_STRVAR(length_cue_doc, "Private method returning an estimate of len(list(it)).");
2350+
2351+
static PyMethodDef repeat_methods[] = {
2352+
{"_length_cue", (PyCFunction)repeat_len, METH_NOARGS, length_cue_doc},
2353+
{NULL, NULL} /* sentinel */
23502354
};
23512355

23522356
PyDoc_STRVAR(repeat_doc,
@@ -2368,7 +2372,7 @@ static PyTypeObject repeat_type = {
23682372
0, /* tp_compare */
23692373
(reprfunc)repeat_repr, /* tp_repr */
23702374
0, /* tp_as_number */
2371-
&repeat_as_sequence, /* tp_as_sequence */
2375+
0, /* tp_as_sequence */
23722376
0, /* tp_as_mapping */
23732377
0, /* tp_hash */
23742378
0, /* tp_call */
@@ -2385,7 +2389,7 @@ static PyTypeObject repeat_type = {
23852389
0, /* tp_weaklistoffset */
23862390
PyObject_SelfIter, /* tp_iter */
23872391
(iternextfunc)repeat_next, /* tp_iternext */
2388-
0, /* tp_methods */
2392+
repeat_methods, /* tp_methods */
23892393
0, /* tp_members */
23902394
0, /* tp_getset */
23912395
0, /* tp_base */

Objects/abstract.c

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,31 @@ PyObject_Length(PyObject *o)
8181
}
8282
#define PyObject_Length PyObject_Size
8383

84+
int
85+
_PyObject_LengthCue(PyObject *o)
86+
{
87+
int rv = PyObject_Size(o);
88+
if (rv != -1)
89+
return rv;
90+
if (PyErr_ExceptionMatches(PyExc_TypeError) ||
91+
PyErr_ExceptionMatches(PyExc_AttributeError)) {
92+
PyObject *err_type, *err_value, *err_tb, *ro;
93+
94+
PyErr_Fetch(&err_type, &err_value, &err_tb);
95+
ro = PyObject_CallMethod(o, "_length_cue", NULL);
96+
if (ro != NULL) {
97+
rv = (int)PyInt_AsLong(ro);
98+
Py_DECREF(ro);
99+
Py_XDECREF(err_type);
100+
Py_XDECREF(err_value);
101+
Py_XDECREF(err_tb);
102+
return rv;
103+
}
104+
PyErr_Restore(err_type, err_value, err_tb);
105+
}
106+
return -1;
107+
}
108+
84109
PyObject *
85110
PyObject_GetItem(PyObject *o, PyObject *key)
86111
{
@@ -1399,7 +1424,7 @@ PySequence_Tuple(PyObject *v)
13991424
return NULL;
14001425

14011426
/* Guess result size and allocate space. */
1402-
n = PyObject_Size(v);
1427+
n = _PyObject_LengthCue(v);
14031428
if (n < 0) {
14041429
if (!PyErr_ExceptionMatches(PyExc_TypeError) &&
14051430
!PyErr_ExceptionMatches(PyExc_AttributeError)) {

Objects/dictobject.c

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2054,17 +2054,20 @@ dictiter_dealloc(dictiterobject *di)
20542054
PyObject_Del(di);
20552055
}
20562056

2057-
static int
2057+
static PyObject *
20582058
dictiter_len(dictiterobject *di)
20592059
{
2060+
int len = 0;
20602061
if (di->di_dict != NULL && di->di_used == di->di_dict->ma_used)
2061-
return di->len;
2062-
return 0;
2062+
len = di->len;
2063+
return PyInt_FromLong(len);
20632064
}
20642065

2065-
static PySequenceMethods dictiter_as_sequence = {
2066-
(inquiry)dictiter_len, /* sq_length */
2067-
0, /* sq_concat */
2066+
PyDoc_STRVAR(length_cue_doc, "Private method returning an estimate of len(list(it)).");
2067+
2068+
static PyMethodDef dictiter_methods[] = {
2069+
{"_length_cue", (PyCFunction)dictiter_len, METH_NOARGS, length_cue_doc},
2070+
{NULL, NULL} /* sentinel */
20682071
};
20692072

20702073
static PyObject *dictiter_iternextkey(dictiterobject *di)
@@ -2120,7 +2123,7 @@ PyTypeObject PyDictIterKey_Type = {
21202123
0, /* tp_compare */
21212124
0, /* tp_repr */
21222125
0, /* tp_as_number */
2123-
&dictiter_as_sequence, /* tp_as_sequence */
2126+
0, /* tp_as_sequence */
21242127
0, /* tp_as_mapping */
21252128
0, /* tp_hash */
21262129
0, /* tp_call */
@@ -2136,6 +2139,8 @@ PyTypeObject PyDictIterKey_Type = {
21362139
0, /* tp_weaklistoffset */
21372140
PyObject_SelfIter, /* tp_iter */
21382141
(iternextfunc)dictiter_iternextkey, /* tp_iternext */
2142+
dictiter_methods, /* tp_methods */
2143+
0,
21392144
};
21402145

21412146
static PyObject *dictiter_iternextvalue(dictiterobject *di)
@@ -2191,7 +2196,7 @@ PyTypeObject PyDictIterValue_Type = {
21912196
0, /* tp_compare */
21922197
0, /* tp_repr */
21932198
0, /* tp_as_number */
2194-
&dictiter_as_sequence, /* tp_as_sequence */
2199+
0, /* tp_as_sequence */
21952200
0, /* tp_as_mapping */
21962201
0, /* tp_hash */
21972202
0, /* tp_call */
@@ -2207,6 +2212,8 @@ PyTypeObject PyDictIterValue_Type = {
22072212
0, /* tp_weaklistoffset */
22082213
PyObject_SelfIter, /* tp_iter */
22092214
(iternextfunc)dictiter_iternextvalue, /* tp_iternext */
2215+
dictiter_methods, /* tp_methods */
2216+
0,
22102217
};
22112218

22122219
static PyObject *dictiter_iternextitem(dictiterobject *di)
@@ -2276,7 +2283,7 @@ PyTypeObject PyDictIterItem_Type = {
22762283
0, /* tp_compare */
22772284
0, /* tp_repr */
22782285
0, /* tp_as_number */
2279-
&dictiter_as_sequence, /* tp_as_sequence */
2286+
0, /* tp_as_sequence */
22802287
0, /* tp_as_mapping */
22812288
0, /* tp_hash */
22822289
0, /* tp_call */
@@ -2292,4 +2299,6 @@ PyTypeObject PyDictIterItem_Type = {
22922299
0, /* tp_weaklistoffset */
22932300
PyObject_SelfIter, /* tp_iter */
22942301
(iternextfunc)dictiter_iternextitem, /* tp_iternext */
2302+
dictiter_methods, /* tp_methods */
2303+
0,
22952304
};

Objects/enumobject.c

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -239,23 +239,25 @@ PyDoc_STRVAR(reversed_doc,
239239
"\n"
240240
"Return a reverse iterator");
241241

242-
static int
242+
static PyObject *
243243
reversed_len(reversedobject *ro)
244244
{
245245
int position, seqsize;
246246

247247
if (ro->seq == NULL)
248-
return 0;
248+
return PyInt_FromLong(0);
249249
seqsize = PySequence_Size(ro->seq);
250250
if (seqsize == -1)
251-
return -1;
251+
return NULL;
252252
position = ro->index + 1;
253-
return (seqsize < position) ? 0 : position;
253+
return PyInt_FromLong((seqsize < position) ? 0 : position);
254254
}
255255

256-
static PySequenceMethods reversed_as_sequence = {
257-
(inquiry)reversed_len, /* sq_length */
258-
0, /* sq_concat */
256+
PyDoc_STRVAR(length_cue_doc, "Private method returning an estimate of len(list(it)).");
257+
258+
static PyMethodDef reversediter_methods[] = {
259+
{"_length_cue", (PyCFunction)reversed_len, METH_NOARGS, length_cue_doc},
260+
{NULL, NULL} /* sentinel */
259261
};
260262

261263
PyTypeObject PyReversed_Type = {
@@ -272,7 +274,7 @@ PyTypeObject PyReversed_Type = {
272274
0, /* tp_compare */
273275
0, /* tp_repr */
274276
0, /* tp_as_number */
275-
&reversed_as_sequence, /* tp_as_sequence */
277+
0, /* tp_as_sequence */
276278
0, /* tp_as_mapping */
277279
0, /* tp_hash */
278280
0, /* tp_call */
@@ -289,7 +291,7 @@ PyTypeObject PyReversed_Type = {
289291
0, /* tp_weaklistoffset */
290292
PyObject_SelfIter, /* tp_iter */
291293
(iternextfunc)reversed_next, /* tp_iternext */
292-
0, /* tp_methods */
294+
reversediter_methods, /* tp_methods */
293295
0, /* tp_members */
294296
0, /* tp_getset */
295297
0, /* tp_base */

0 commit comments

Comments
 (0)