Repository navigation
Description
Feature
itertools.tee() gets the order and the count of its iter() calls wrong: a negative n is reported only after iter(iterable) succeeds, and an iterator whose own __iter__ is broken is accepted. CPython checks n first, returns () for n == 0 without calling iter(), and then calls iter() once on the iterable and once more on the iterator it gets back.
Reproduction
import itertools
class NoneIter:
def __iter__(self):
return None
class BadIterator:
def __iter__(self):
return None
def __next__(self):
return None
class BadIterable:
def __iter__(self):
return BadIterator()
try:
print(itertools.tee(NoneIter(), -1))
except Exception as e:
print(f"{type(e).__name__}: {e}")
try:
print(type(itertools.tee(BadIterable())))
except Exception as e:
print(f"{type(e).__name__}: {e}")RustPython:
TypeError: NoneIter.__iter__() must return an iterator, not NoneType
<class 'tuple'>
CPython:
ValueError: n cannot be negative
TypeError: BadIterator.__iter__() must return an iterator, not NoneType
tee(NoneIter(), 0) raises the same TypeError instead of returning (), and an iterator that defines __copy__ has it called by tee(), which CPython stopped doing in gh-123884.
Root cause
TeeArgs declares iterable as PyIter, so iter() runs during argument binding, before tee checks n. tee then copies an iterator with __copy__ directly, and PyItertoolsTee::from_iter stores any other iterator without calling iter() on it, where CPython's tee_fromiterable calls PyObject_GetIter on the iterator it is given.
Fix
Bind iterable as PyObjectRef, return an empty tuple for n == 0, call get_iter only after that, drop the __copy__ shortcut, and call get_iter again at the top of from_iter.
Environment
- RustPython 713e14c (Python 3.15.0a), also reproduced on 0.4.0 and 0.6.1
- CPython v3.15.2, also v3.14.4 and v3.12.13 (those say
n must be >= 0anditer() returned non-iterator of type 'NoneType') - OS: macOS arm64 (713e14c), Debian 12 (0.4.0, 0.6.1)
Python Documentation or reference to CPython source code
-
CPython
itertools_tee_impl:Modules/itertoolsmodule.c#L1161-L1179returns forn == 0before callingiter(), after the generated wrapper has rejected a negativen(Modules/clinic/itertoolsmodule.c.h#L348-L352):result = PyTuple_New(n); if (result == NULL) return NULL; if (n == 0) return result; it = PyObject_GetIter(iterable);
-
CPython
tee_fromiterable:Modules/itertoolsmodule.c#L1051-L1058callsiter()again on the iteratortee()passes it:it = PyObject_GetIter(iterable); if (it == NULL) return NULL;
-
Documentation:
itertools.tee
AI disclosure: this report was drafted with Claude Code (claude-opus-5-5) and reviewed by me before submission. The divergence comes from a differential-testing run against CPython 3.12.13, and every source reference above was checked against the linked revisions.
Activity