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

Skip to content

Commit aa9a79d

Browse files
committed
Issue #16148: implemented PEP 424
1 parent ef08fb1 commit aa9a79d

14 files changed

Lines changed: 160 additions & 81 deletions

File tree

Doc/c-api/object.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,13 @@ is considered sufficient for this determination.
342342
returned. This is the equivalent to the Python expression ``len(o)``.
343343
344344
345+
.. c:function:: Py_ssize_t PyObject_LengthHint(PyObject *o, Py_ssize_t default)
346+
347+
Return an estimated length for the object *o*. First trying to return its
348+
actual length, then an estimate using ``__length_hint__``, and finally
349+
returning the default value. On error ``-1`` is returned. This is the
350+
equivalent to the Python expression ``operator.length_hint(o, default)``.
351+
345352
.. c:function:: PyObject* PyObject_GetItem(PyObject *o, PyObject *key)
346353
347354
Return element of *o* corresponding to the object *key* or *NULL* on failure.

Doc/library/operator.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,12 @@ their character equivalents.
235235

236236
.. XXX: find a better, readable, example
237237
238+
.. function:: length_hint(obj, default=0)
239+
240+
Return an estimated length for the object *o*. First trying to return its
241+
actual length, then an estimate using ``__length_hint__``, and finally
242+
returning the default value.
243+
238244
The :mod:`operator` module also defines tools for generalized attribute and item
239245
lookups. These are useful for making fast field extractors as arguments for
240246
:func:`map`, :func:`sorted`, :meth:`itertools.groupby`, or other functions that

Include/abstract.h

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -403,9 +403,8 @@ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx*/
403403
PyAPI_FUNC(Py_ssize_t) PyObject_Length(PyObject *o);
404404
#define PyObject_Length PyObject_Size
405405

406-
#ifndef Py_LIMITED_API
407-
PyAPI_FUNC(Py_ssize_t) _PyObject_LengthHint(PyObject *o, Py_ssize_t);
408-
#endif
406+
PyAPI_FUNC(int) _PyObject_HasLen(PyObject *o);
407+
PyAPI_FUNC(Py_ssize_t) PyObject_LengthHint(PyObject *o, Py_ssize_t);
409408

410409
/*
411410
Guess the size of object o using len(o) or o.__length_hint__().

Lib/test/test_enumerate.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import unittest
2+
import operator
23
import sys
34
import pickle
45

@@ -168,15 +169,13 @@ def test_range_optimization(self):
168169
x = range(1)
169170
self.assertEqual(type(reversed(x)), type(iter(x)))
170171

171-
@support.cpython_only
172172
def test_len(self):
173173
# This is an implementation detail, not an interface requirement
174-
from test.test_iterlen import len
175174
for s in ('hello', tuple('hello'), list('hello'), range(5)):
176-
self.assertEqual(len(reversed(s)), len(s))
175+
self.assertEqual(operator.length_hint(reversed(s)), len(s))
177176
r = reversed(s)
178177
list(r)
179-
self.assertEqual(len(r), 0)
178+
self.assertEqual(operator.length_hint(r), 0)
180179
class SeqWithWeirdLen:
181180
called = False
182181
def __len__(self):
@@ -187,7 +186,7 @@ def __len__(self):
187186
def __getitem__(self, index):
188187
return index
189188
r = reversed(SeqWithWeirdLen())
190-
self.assertRaises(ZeroDivisionError, len, r)
189+
self.assertRaises(ZeroDivisionError, operator.length_hint, r)
191190

192191

193192
def test_gc(self):

Lib/test/test_iterlen.py

Lines changed: 29 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -45,31 +45,21 @@
4545
from test import support
4646
from itertools import repeat
4747
from collections import deque
48-
from builtins import len as _len
48+
from operator import length_hint
4949

5050
n = 10
5151

52-
def len(obj):
53-
try:
54-
return _len(obj)
55-
except TypeError:
56-
try:
57-
# note: this is an internal undocumented API,
58-
# don't rely on it in your own programs
59-
return obj.__length_hint__()
60-
except AttributeError:
61-
raise TypeError
6252

6353
class TestInvariantWithoutMutations(unittest.TestCase):
6454

6555
def test_invariant(self):
6656
it = self.it
6757
for i in reversed(range(1, n+1)):
68-
self.assertEqual(len(it), i)
58+
self.assertEqual(length_hint(it), i)
6959
next(it)
70-
self.assertEqual(len(it), 0)
60+
self.assertEqual(length_hint(it), 0)
7161
self.assertRaises(StopIteration, next, it)
72-
self.assertEqual(len(it), 0)
62+
self.assertEqual(length_hint(it), 0)
7363

7464
class TestTemporarilyImmutable(TestInvariantWithoutMutations):
7565

@@ -78,12 +68,12 @@ def test_immutable_during_iteration(self):
7868
# length immutability during iteration
7969

8070
it = self.it
81-
self.assertEqual(len(it), n)
71+
self.assertEqual(length_hint(it), n)
8272
next(it)
83-
self.assertEqual(len(it), n-1)
73+
self.assertEqual(length_hint(it), n-1)
8474
self.mutate()
8575
self.assertRaises(RuntimeError, next, it)
86-
self.assertEqual(len(it), 0)
76+
self.assertEqual(length_hint(it), 0)
8777

8878
## ------- Concrete Type Tests -------
8979

@@ -92,10 +82,6 @@ class TestRepeat(TestInvariantWithoutMutations):
9282
def setUp(self):
9383
self.it = repeat(None, n)
9484

95-
def test_no_len_for_infinite_repeat(self):
96-
# The repeat() object can also be infinite
97-
self.assertRaises(TypeError, len, repeat(None))
98-
9985
class TestXrange(TestInvariantWithoutMutations):
10086

10187
def setUp(self):
@@ -167,14 +153,15 @@ def test_mutation(self):
167153
it = iter(d)
168154
next(it)
169155
next(it)
170-
self.assertEqual(len(it), n-2)
156+
self.assertEqual(length_hint(it), n - 2)
171157
d.append(n)
172-
self.assertEqual(len(it), n-1) # grow with append
158+
self.assertEqual(length_hint(it), n - 1) # grow with append
173159
d[1:] = []
174-
self.assertEqual(len(it), 0)
160+
self.assertEqual(length_hint(it), 0)
175161
self.assertEqual(list(it), [])
176162
d.extend(range(20))
177-
self.assertEqual(len(it), 0)
163+
self.assertEqual(length_hint(it), 0)
164+
178165

179166
class TestListReversed(TestInvariantWithoutMutations):
180167

@@ -186,32 +173,41 @@ def test_mutation(self):
186173
it = reversed(d)
187174
next(it)
188175
next(it)
189-
self.assertEqual(len(it), n-2)
176+
self.assertEqual(length_hint(it), n - 2)
190177
d.append(n)
191-
self.assertEqual(len(it), n-2) # ignore append
178+
self.assertEqual(length_hint(it), n - 2) # ignore append
192179
d[1:] = []
193-
self.assertEqual(len(it), 0)
180+
self.assertEqual(length_hint(it), 0)
194181
self.assertEqual(list(it), []) # confirm invariant
195182
d.extend(range(20))
196-
self.assertEqual(len(it), 0)
183+
self.assertEqual(length_hint(it), 0)
197184

198185
## -- Check to make sure exceptions are not suppressed by __length_hint__()
199186

200187

201188
class BadLen(object):
202-
def __iter__(self): return iter(range(10))
189+
def __iter__(self):
190+
return iter(range(10))
191+
203192
def __len__(self):
204193
raise RuntimeError('hello')
205194

195+
206196
class BadLengthHint(object):
207-
def __iter__(self): return iter(range(10))
197+
def __iter__(self):
198+
return iter(range(10))
199+
208200
def __length_hint__(self):
209201
raise RuntimeError('hello')
210202

203+
211204
class NoneLengthHint(object):
212-
def __iter__(self): return iter(range(10))
205+
def __iter__(self):
206+
return iter(range(10))
207+
213208
def __length_hint__(self):
214-
return None
209+
return NotImplemented
210+
215211

216212
class TestLengthHintExceptions(unittest.TestCase):
217213

Lib/test/test_itertools.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1723,9 +1723,8 @@ def test_tee(self):
17231723
class LengthTransparency(unittest.TestCase):
17241724

17251725
def test_repeat(self):
1726-
from test.test_iterlen import len
1727-
self.assertEqual(len(repeat(None, 50)), 50)
1728-
self.assertRaises(TypeError, len, repeat(None))
1726+
self.assertEqual(operator.length_hint(repeat(None, 50)), 50)
1727+
self.assertEqual(operator.length_hint(repeat(None), 12), 12)
17291728

17301729
class RegressionTests(unittest.TestCase):
17311730

Lib/test/test_operator.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,31 @@ def __getitem__(self, other): return 5 # so that C is a sequence
410410
self.assertEqual(operator.__ixor__ (c, 5), "ixor")
411411
self.assertEqual(operator.__iconcat__ (c, c), "iadd")
412412

413+
def test_length_hint(self):
414+
class X(object):
415+
def __init__(self, value):
416+
self.value = value
417+
418+
def __length_hint__(self):
419+
if type(self.value) is type:
420+
raise self.value
421+
else:
422+
return self.value
423+
424+
self.assertEqual(operator.length_hint([], 2), 0)
425+
self.assertEqual(operator.length_hint(iter([1, 2, 3])), 3)
426+
427+
self.assertEqual(operator.length_hint(X(2)), 2)
428+
self.assertEqual(operator.length_hint(X(NotImplemented), 4), 4)
429+
self.assertEqual(operator.length_hint(X(TypeError), 12), 12)
430+
with self.assertRaises(TypeError):
431+
operator.length_hint(X("abc"))
432+
with self.assertRaises(ValueError):
433+
operator.length_hint(X(-2))
434+
with self.assertRaises(LookupError):
435+
operator.length_hint(X(LookupError))
436+
437+
413438
def test_main(verbose=None):
414439
import sys
415440
test_classes = (

Lib/test/test_set.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -848,8 +848,6 @@ def test_iteration(self):
848848
for v in self.set:
849849
self.assertIn(v, self.values)
850850
setiter = iter(self.set)
851-
# note: __length_hint__ is an internal undocumented API,
852-
# don't rely on it in your own programs
853851
self.assertEqual(setiter.__length_hint__(), len(self.set))
854852

855853
def test_pickling(self):

Modules/operator.c

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,31 @@ _tscmp(const unsigned char *a, const unsigned char *b,
208208
return (result == 0);
209209
}
210210

211+
PyDoc_STRVAR(length_hint__doc__,
212+
"length_hint(obj, default=0) -> int\n"
213+
"Return an estimate of the number of items in obj.\n"
214+
"This is useful for presizing containers when building from an\n"
215+
"iterable.\n"
216+
"\n"
217+
"If the object supports len(), the result will be\n"
218+
"exact. Otherwise, it may over- or under-estimate by an\n"
219+
"arbitrary amount. The result will be an integer >= 0.");
220+
221+
static PyObject *length_hint(PyObject *self, PyObject *args)
222+
{
223+
PyObject *obj;
224+
Py_ssize_t defaultvalue = 0, res;
225+
if (!PyArg_ParseTuple(args, "O|n:length_hint", &obj, &defaultvalue)) {
226+
return NULL;
227+
}
228+
res = PyObject_LengthHint(obj, defaultvalue);
229+
if (res == -1 && PyErr_Occurred()) {
230+
return NULL;
231+
}
232+
return PyLong_FromSsize_t(res);
233+
}
234+
235+
211236
PyDoc_STRVAR(compare_digest__doc__,
212237
"compare_digest(a, b) -> bool\n"
213238
"\n"
@@ -366,6 +391,8 @@ spam2(ge,__ge__, "ge(a, b) -- Same as a>=b.")
366391

367392
{"_compare_digest", (PyCFunction)compare_digest, METH_VARARGS,
368393
compare_digest__doc__},
394+
{"length_hint", (PyCFunction)length_hint, METH_VARARGS,
395+
length_hint__doc__},
369396
{NULL, NULL} /* sentinel */
370397

371398
};

0 commit comments

Comments
 (0)