1

What I Want to Do:

Check the different modules I've imported in my script (Python 2 or 3), and print their names. I'm currently using Python 2.7.12 and 3.6.4.

Where I Started:

The script simply imports modules, uses list comprehension to find what's there, and prints a list:

#!/usr/bin/env python

import sys
import os
import types

module_list = [i.__name__ for i in globals().values()
               if type(i) == types.ModuleType and i in sys.modules.values()]
print(module_list)

I was initially running this in Python 3, and it works just fine (also fine for Python 2). The output is:

['__builtin__', 'sys', 'os', 'types']  # Python 2.
['builtins', 'sys', 'os', 'types']     # Python 3.

At this point, I decided to expand the list comprehension out, as I find it to be good practice in deciphering code/logical flow. So I replaced the single statement with:

module_list = []
for i in globals().values():
    if type(i) == types.ModuleType and i in sys.modules.values():
        module_list.append(i.__name__)

Here's where things get hairy. I get a RuntimeError, and the output is now:

...
    for i in globals().values():
RuntimeError: dictionary changed size during iteration

However, it still works in Python 2. I'm now more curious than anything else, and tried a few things.

What I Tried:

  • Turned globals().values() into a list:

    for i in list(globals().values()):
        if type(i) == types.ModuleType and i in sys.modules.values():
            module_list.append(i.__name__)
    

    This works as intended in Python 2 or 3.

  • Removed the list() and added in proper main structure:

    def main():
        module_list = []
        for i in globals().values():
            if type(i) == type(sys) and i in sys.modules.values():
                module_list.append(i.__name__)
        print(module_list)
    
    if __name__ == '__main__':
        main()
    

    This works as intended in Python 2 and 3.

To Recap:

  • List comprehension works for Python 2 and 3.
  • The for loop only works for Python 2.
  • Wrapping in a list works for Python 2 and 3.
  • Proper main structuring works for Python 2 and 3.

The Question:

What is it about that for loop in Python 3 that's breaking things?

erekalper
  • 857
  • 9
  • 22
  • This may be of interest: https://stackoverflow.com/q/11941817/8146556 Also a good article on this topic here: https://cito.github.io/blog/never-iterate-a-changing-dict/ – rahlf23 Mar 29 '18 at 19:36

1 Answers1

1

for i in globals().values(): - i is added to the global namespace after you get the values iterator. If you put i = None before the loop, or do for i in list(globals().values()):, it works.

In python 2, values() returns a list of values, but in python 3 its an iterator. That iterator detects the change and raises the error.

tdelaney
  • 73,364
  • 6
  • 83
  • 116