Skip to content

itertools.tee() calls iter() before checking n, and never calls it on the iterator it buffers #9512

Closed

Description

@jseop-lim

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 >= 0 and iter() 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

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

  1. added
    C-compatA discrepancy between RustPython and CPython
    on Nov 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    C-compatA discrepancy between RustPython and CPython

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions