Defining iterator in a separate class no longer works in 3.13 #128161
Copy link
Copy link
Closed
Labels
3.13only security fixesonly security fixes3.14bugs and security fixesbugs and security fixesinterpreter-core(Objects, Python, Grammar, and Parser dirs)(Objects, Python, Grammar, and Parser dirs)type-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error
Description
Bug report
Bug description:
Defining an interator for a class in a separate class no longer works properly in 3.13. With the following test_iter.py:
class list2(list):
def __iter__(self):
return list2iterator(self)
class list2iterator:
def __init__(self, X):
self._X = X
self._pointer = -1
def __next__(self):
self._pointer += 1
if self._pointer == len(self._X):
self._pointer = -1
raise StopIteration
return self._X[self._pointer]With Python 3.13.1 one gets:
>>> from test_iter import list2
>>> X=list2([1,2,3])
>>> [x for x in X]
Traceback (most recent call last):
File "<python-input-2>", line 1, in <module>
[x for x in X]
^
TypeError: 'list2iterator' object is not iterableWith Python 3.12.7 it works:
>>> from test_iter import list2
>>> X=list2([1,2,3])
>>> [x for x in X]
[1, 2, 3]Bisected to bcc7227e
CPython versions tested on:
3.12, 3.13
Operating systems tested on:
Linux
Linked PRs
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
3.13only security fixesonly security fixes3.14bugs and security fixesbugs and security fixesinterpreter-core(Objects, Python, Grammar, and Parser dirs)(Objects, Python, Grammar, and Parser dirs)type-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error
Activity
Eclips4 commentedon Dec 21, 2024
cc @efimov-mikhail @markshannon @JelleZijlstra
ronaldoussoren commentedon Dec 21, 2024
https://docs.python.org/3/library/stdtypes.html#iterator-types says that iterators must also implement
__iter__(returningself). That's a long standing requirement on iterators, and not one introduced in 3.13.antonio-rojas commentedon Dec 21, 2024
Alright, thanks
Eclips4 commentedon Dec 22, 2024
@ronaldoussoren You're right, and this code is definitely doing something weird, but it works in 3.12:
example.py:
3.12.8:
3.13.1:
Though it probably shouldn't work, it is definitely a regression.
Bytecode for
[x for x in y]3.12.8:3.13.1:
ronaldoussoren commentedon Dec 22, 2024
Right, the code from the OP is broken according to the data model, but we still did a change that's not backward compatible. I'm not deep enough into this to fully understand the ramifications.
efimov-mikhail commentedon Dec 22, 2024
This behavior change is a direct consequence of change in bytecode generation in my PR. I don't think this is a bug.
terryjreedy commentedon Dec 22, 2024
The exact example worked in the CPython implementation of 3.12, but that may be an accident, as it should not have by the language definition. It might have already failed in 3.12 in other implementations. In any case, adding the following exposes the defect in list2iterable even in 3.12.
If there is still a question about changing anything, Guido might remember the original design discussions.
pochmann3 commentedon Dec 23, 2024
Relevant bit from glossary: iterator:
7 remaining items
Adding a __iter__ method to skia.Iter
efimov-mikhail commentedon May 26, 2025
I think we should decide what to do with this issue.
FYI, on current CPython 3.12 branch this code snippet works, but on 3.13, 3.14 and main branches it doesn't.
@picnixz @Eclips4 @markshannon
Basically, there are two ways which I can see as reliable:
And some links on related issues and PRs:
#125038
#125178
#132351
efimov-mikhail commentedon May 26, 2025
And maybe it's reasonable to do some changes in docs?
Some clear mention about generator expressions like
(x for x in None), for example.They aren't correct in Python 3.13.3 and before, an exception will be raised on this expression in code.
But on current 3.13 branch this code is correct.
Maybe it's better to remove all related changes in 3.13 branch?
And have exactly the same behavior as on 3.12?
@hugovk @terryjreedy @JelleZijlstra
donhatch commentedon May 27, 2025
The issue title could perhaps be improved.
Current title: "Defining iterator in a separate class no longer works in 3.13"
Suggested new title: "Iterator without
__iter__no longer works in 3.13.1"Details:
__iter__member functiondonhatch commentedon May 27, 2025
The title and text of committed #132351 seems to imply that maybe it fixes this, i.e. reverts the behavior to pre-3.13.1 in which the example code works (because the generated bytcode has no redundant GET_ITER on something already known to be an iterator).
Is that correct?
That would make me very happy, since I would very much like to go back to happily ignoring the IMO ill-advised "requirement" that iterators be iterable (in fact it is my hope that some day that requirement might be officially removed, for a brighter less-confusing future in which Iterator and Iterable are two clearly distinct non-overlapping concepts).
donhatch commentedon May 27, 2025
@efimov-mikhail wrote:
(Unfortunately I don't currently have anything higher than 3.13.0 to try this out on...)
In 3.13.0 I see that
(x for x in None)throwsTypeError: 'NoneType' object is not iterablewhich seems reasonable to me.Are you saying that, in later versions, it returns something instead of throwing, and that it's correct? Why?
efimov-mikhail commentedon May 27, 2025
No, this is not correct.
On current main branch
This is because of changes in bytecode.
You can see two
GET_ITERbytecodes for list comprehension here.It sounds reasonable for me. Although, this is not the only change.
Yes, on current main, 3.14 and 3.13 branches we can see the following result:
You can see some motivation here:
#125178 (comment)
#132351 (comment)
cc @markshannon @sobolevn
[3.14] GH-128161: Remove redundant GET_ITER from list comprehension c…
[3.14] pythonGH-128161: Remove redundant GET_ITER from list comprehen…
[3.13] gh-128161: Remove redundant GET_ITER from list comprehension c…
[3.13] GH-128161: Fix refleak introduced in GH-134788 (GH-134799)
Roman513 commentedon Jun 8, 2025
This is fixed in 3.13.4:
But in 3.14.0b2 it sill does not work.
However it could be reintroduced again, related issue #135171 and fix #135225
If there's a decision how this should work, do we need some additional tests to make the behavior stable?
cc: @picnixz @markshannon
serhiy-storchaka commentedon Jun 10, 2025
Whatever we decide on the
mainbranch, you're better off adding an__iter__method returningselfto your iterator. There's no guarantee that this will always work in CPython or other implementations.efimov-mikhail commentedon Jun 22, 2025
I've started related d.p.o. thread here: https://discuss.python.org/t/generator-expressions-behavior-with-non-iterable-objects/96329.