-
-
Notifications
You must be signed in to change notification settings - Fork 32.1k
Defining iterator in a separate class no longer works in 3.13 #128161
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
https://docs.python.org/3/library/stdtypes.html#iterator-types says that iterators must also implement |
Alright, thanks |
@ronaldoussoren You're right, and this code is definitely doing something weird, but it works in 3.12: 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]
target = list2([1, 2, 3])
for i in target:
print(i)
[print(i) for i in target] 3.12.8: eclips4@suffering ~/tmp> python3.12 example.py
1
2
3
1
2
3 3.13.1: 1
2
3
Traceback (most recent call last):
File "/Users/eclips4/tmp/example.py", line 23, in <module>
[print(i) for i in target]
^^^^^^
TypeError: 'list2iterator' object is not iterable Though it probably shouldn't work, it is definitely a regression. Bytecode for 0 0 RESUME 0
1 2 LOAD_NAME 0 (y)
4 GET_ITER
6 LOAD_FAST_AND_CLEAR 0 (x)
8 SWAP 2
10 BUILD_LIST 0
12 SWAP 2
>> 14 FOR_ITER 4 (to 26)
18 STORE_FAST 0 (x)
20 LOAD_FAST 0 (x)
22 LIST_APPEND 2
24 JUMP_BACKWARD 6 (to 14)
>> 26 END_FOR
28 SWAP 2
30 STORE_FAST 0 (x)
32 RETURN_VALUE
>> 34 SWAP 2
36 POP_TOP
38 SWAP 2
40 STORE_FAST 0 (x)
42 RERAISE 0
ExceptionTable:
10 to 26 -> 34 [2] 3.13.1: 0 RESUME 0
1 LOAD_NAME 0 (y)
GET_ITER
LOAD_FAST_AND_CLEAR 0 (x)
SWAP 2
L1: BUILD_LIST 0
SWAP 2
GET_ITER - problematic instruction
L2: FOR_ITER 4 (to L3)
STORE_FAST_LOAD_FAST 0 (x, x)
LIST_APPEND 2
JUMP_BACKWARD 6 (to L2)
L3: END_FOR
POP_TOP
L4: SWAP 2
STORE_FAST 0 (x)
RETURN_VALUE
-- L5: SWAP 2
POP_TOP
1 SWAP 2
STORE_FAST 0 (x)
RERAISE 0
ExceptionTable:
L1 to L4 -> L5 [2] |
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. |
This behavior change is a direct consequence of change in bytecode generation in my PR. I don't think this is a bug. |
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. |
Relevant bit from glossary: iterator:
|
Right. A defective iterator may or may not work, so skip the requirement at your own risk. Int caching is another implementation detail. Implementation details are 1. subject to change and 2. are sometimes noted to discourage the filing of issues. I think the iterator note followed a discussion and decision that the inconsistency should not be treated as a bug. But the note also reaffirms the requirement. Removing it, at least for for-loops, would have required other implementations and future CPythons to allow the omission. All that is needed for list2iterator is |
FWIW, the OP's code also works in PyPy3, so this is more than a C implementation detail. |
I think that the original change was not needed snd should be reverted. It just slows dowsn everyone's code. |
See python/cpython#128161 "Defining iterator in a separate class no longer works in 3.13" We have iterator for SkTextBlob defined by SkTextBlob::Iter(textblob), which is the c++/pybind11 equivalent of the same situation. Following the suggestion: python/cpython#128161 (comment) Also see actions/runner-images#11241 Fixes kyamagu#295
Well, I am somewhat unhappy about this as we have 4-year old code that works on all python versions from 3.8 to 3.13 until last month; then it broke with 3.13.1; I thought mistakenly that github did something stupid in their CI images between 3.13 and 3.13.1. Never thought that python would break existing code within a minor version upgrade... |
Considering we broke something in a minor release, we should probably consider reverting the change. In addition, we ourself say that we don't consistently apply the requirement on iterators. So even if it's a data model requirement... I think it would be nicer to users. Considering the bytecode generation stuff was for a probably rarer usecase, I would prefer the above code to work instead. |
See python/cpython#128161 "Defining iterator in a separate class no longer works in 3.13" We have iterator for SkTextBlob defined by SkTextBlob::Iter(textblob), which is the c++/pybind11 equivalent of the same situation. Following the suggestion: python/cpython#128161 (comment) Also see actions/runner-images#11241 Fixes kyamagu#295
See python/cpython#128161 "Defining iterator in a separate class no longer works in 3.13" We have iterator for SkTextBlob defined by SkTextBlob::Iter(textblob), which is the c++/pybind11 equivalent of the same situation. Following the suggestion: python/cpython#128161 (comment) Also see actions/runner-images#11241 Fixes #295
I think we should decide what to do with this issue. @picnixz @Eclips4 @markshannon Basically, there are two ways which I can see as reliable:
And some links on related issues and PRs: |
And maybe it's reasonable to do some changes in docs? Maybe it's better to remove all related changes in 3.13 branch? |
The issue title could perhaps be improved. Current title: "Defining iterator in a separate class no longer works in 3.13" Details:
|
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). |
@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 |
No, this is not correct. -> % cat test_list2.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]
[print(i) for i in target]
-> % ./python test_list2.py
Traceback (most recent call last):
File "/home/sikko/projects/cpython/test_iter.py", line 21, in <module>
[print(i) for i in target]
^^^^^^
TypeError: 'list2iterator' object is not iterable
>>> dis.dis('[print(i) for i in target]')
0 RESUME 0
1 LOAD_NAME 0 (target)
GET_ITER
LOAD_FAST_AND_CLEAR 0 (i)
SWAP 2
L1: BUILD_LIST 0
SWAP 2
GET_ITER
L2: FOR_ITER 11 (to L3)
STORE_FAST 0 (i)
LOAD_NAME 1 (print)
PUSH_NULL
LOAD_FAST_BORROW 0 (i)
CALL 1
LIST_APPEND 2
JUMP_BACKWARD 13 (to L2)
L3: END_FOR
POP_ITER
L4: SWAP 2
STORE_FAST 0 (i)
RETURN_VALUE
-- L5: SWAP 2
POP_TOP
1 SWAP 2
STORE_FAST 0 (i)
RERAISE 0
ExceptionTable:
L1 to L4 -> L5 [2] This is because of changes in bytecode.
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: >>> g = (x for x in None)
>>> g
<generator object <genexpr> at 0x7f923bb48130>
>>> list(g)
Traceback (most recent call last):
File "<python-input-4>", line 1, in <module>
list(g)
~~~~^^^
File "<python-input-2>", line 1, in <genexpr>
g = (x for x in None)
^^^^
TypeError: 'NoneType' object is not iterable You can see some motivation here: |
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? |
Uh oh!
There was an error while loading. Please reload this page.
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
:With Python 3.13.1 one gets:
With Python 3.12.7 it works:
Bisected to bcc7227e
CPython versions tested on:
3.12, 3.13
Operating systems tested on:
Linux
Linked PRs
The text was updated successfully, but these errors were encountered: