Skip to content
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

Add back list() when iterating over d.items() in a for statement #262

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

yonran
Copy link

@yonran yonran commented Oct 11, 2023

Fix #176.

This reverts commit 8a1ef24 from #120. That commit’s commit message says, “For some reason, 2to3 only treats a for loop as a special context for iter* methods, so it will add a list() call to e.g. for x in d.values().”

There is a good reason that 2to3’s fix_dict considers for statements and for comprehensions a special context (where list() is unnecessary) for iter dict methods but not list dict methods. fix_dict is conservative in omitting list() in d.keys() --> list(d.keys()) and iter() in d.iterkeys() --> iter(d.keys()). For the list functions (python2’s d.keys(), d.items(), d.values()), the list can be safely omitted if the result is consumed immediately by a function (e.g. sorted). For the iterator functions (python2’s d.iterkeys(), d.iteritems(), d.itervalues()), the iter can be safely omitted from the view if the result is converted to an iterator or consumed immediately by a function or by a for statement or for comprehension.

It is often incorrect to omit the list() when converting for x in d.items() to python3 because if you omit list(), then adding or removing elements while iterating will raise “RuntimeError: dictionary changed size during iteration”. Example:

# this is a valid python2 script, but in python3 we must wrap d.keys() in list
d = {'body': '<html>'}
for x in d.keys():
    d['statusCode'] = 200  # raises RuntimeError: dictionary changed size during iteration
for x in d.keys():
    del d[x]  # raises RuntimeError: dictionary changed size during iteration

Furthermore, the overridden in_special_context that considers keys() to be the same as iterkeys() is incorrect because the super method implementation considers iter to be a special context (where adding iter() is not needed) when isiter==True. So iter(d.keys()) should be converted to iter(list(d.keys())), not left unchanged as currently happens.

This reverts commit 8a1ef24.

It is often incorrect to omit the list() when converting for x in d.items() to python3 because if you omit list(), then adding or removing elements while iterating will raise RuntimeError: dictionary changed size during iteration.
""",
"""\
for k in x.items():
pass
for k in list(x.items()):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

really this should be for k in x.copy().items(): because list(x.items()) isn't threadsafe when x.items() was threadsafe on Python 2

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That’s something to consider. copy() does have the advantage of being atomic and being about 10% faster:

# python3.9
>>> import timeit
>>> timeit.timeit(setup='d = dict(zip(range(10000), range(10000)))', stmt='sum(d.copy().keys())', number=10000)
0.814354999999999
>>> timeit.timeit(setup='d = dict(zip(range(10000), range(10000)))', stmt='sum(list(d.keys()))', number=10000)
0.9197422089999989

Downsides of switching to copy():

The point of this PR is to restore the 2to3 fix_dict behavior which identifies all the items(), keys(), and values() that may need to change. After manual review, you may find statements where the list() is unnecessary (and you could have used iteritems() before) or you may switch to d.copy() for concurrency and performance. I think changing from list() to .copy() warrants a separate PR.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@graingert are you requesting me to change it to .copy()?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

dict.items() not converted to list(dict.items()) when iterated over
2 participants