Skip to content

Pure-Python functools.reduce calls iter() twice, unlike the C implementation #161004

Closed as not planned

Description

@jseop-lim

Bug report

The pure-Python functools.reduce raises TypeError when iter(sequence) returns an iterator that defines __next__ but not __iter__, while the C version reduces it.

import operator
import sys

import functools as c_functools
del sys.modules['functools']
sys.modules['_functools'] = None  # block the C accelerator
import functools as py_functools

class Iterator:
    def __init__(self):
        self.items = [1, 2, 3]
    def __next__(self):
        if not self.items:
            raise StopIteration
        return self.items.pop(0)

class Iterable:
    def __iter__(self):
        return Iterator()

print(c_functools.reduce(operator.add, Iterable()))
print(py_functools.reduce(operator.add, Iterable()))
6
Traceback (most recent call last):
  ...
  File ".../functools.py", line 262, in reduce
    for element in it:
                   ^^
TypeError: 'Iterator' object is not iterable

The C functools_reduce calls PyObject_GetIter(seq) once and then only PyIter_Next(it). The Python version calls iter(sequence) and then for element in it:, which calls iter() again on the iterator.

I know the glossary says an iterator must define __iter__, and that CPython does not apply this consistently. I am reporting it because PEP 399 asks the two implementations to behave the same, and PyPy, which uses this module as its reduce, was asked to get the fix here first (pypy/pypy#5701).

A possible fix is to replace the for loop with a while loop that calls next(it) until StopIteration.

CPython versions tested on:

3.14, 3.16, 3.12

Operating systems tested on:

macOS

Linked PRs

Activity

  1. added
    stdlibStandard Library Python modules in the Lib/ directory
    on Oct 31, 2026
  2. picnixz commented on Oct 31, 2026

    @picnixz
    Member

    I know the glossary says an iterator must define __iter__, and that CPython does not apply this consistently

    Mmh. It's a bit hard to say that it's an issue here then. I consider this a broken class in this case. When you say "the two implementations to behave the same" I would expect that to be the case in non-degenerate cases. OTOH, one could imagine a legitimate stateful iterator for which re-calling iter on it may change the semantics, and so we would be still "not behaving similarly".

    OOC, is there any performance impact using a while loop instead? I doubt so but always good to know how calling explicitly next() in a while loop and catching an exception matters.

  3. rhettinger commented on Oct 31, 2026

    @rhettinger
    Contributor

    I think this is a bogus bug report. The iterator protocol specifies that iterators have an __iter__ method that returns self.

  4. picnixz commented on Oct 31, 2026

    @picnixz
    Member

    The iterator protocol specifies that iterators have an iter method that returns self.

    By any chance, does the protocol allow for __iter__ to have side-effects on self? (and thus calling it twice would be annoying). If not, then yes it's not an issue, but otherwise I guess it can be...

  5. rhettinger commented on Oct 31, 2026

    @rhettinger
    Contributor

    By any chance, does the protocol allow for iter to have side-effects on self?

    Calling iter on an iterator just returns self.

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

    stdlibStandard Library Python modules in the Lib/ directorytype-bugAn unexpected behavior, bug, or error

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions