Package import failure in Python 3.5

2019-06-22 12:01发布

问题:

I have the following folder structure:

/main
    main.py
    /io
        __init__.py
        foo.py

In Python 2.7 I would write the following in main.py:

import io.foo

or

from io.foo import *

wheareas in Python 3.5 I get an import error:

Traceback (most recent call last):
  File "./main.py", line 6, in <module>
    import io.foo
ImportError: No module named 'io.foo'; 'io' is not a package

I couldn't find any help so far.

回答1:

io is a built-in module. Don't name your local packages the same as a built-in module.



回答2:

While @ErikCederstrand's answer is correct and probably sufficient for you, I was curious as to why it failed so I went digging through cpython's source. So for any future visitors, here's what I found.

The function where it's failing is here: https://github.com/python/cpython/blob/3.4/Lib/importlib/_bootstrap.py#L2207

On line 2209, it checks to see if the parent module has been loaded:

parent = name.rpartition('.')[0]  #  Value of 'io'

Since it has loaded the builtin io module, it continues on like normal. After the if returns false, it goes on to assign parent module which again is set to "io":

if name in sys.modules:
    return sys.modules[name]
parent_module = sys.modules[parent]

The next lines are what cause the failure, and it's because builtin modules (io anyway) don't have a __path__ instance variable. The exception you see raised here are ultimately what you're seeing when you run it:

try:
    path = parent_module.__path__
except AttributeError:
    msg = (_ERR_MSG + '; {!r} is not a package').format(name, parent)
    raise ImportError(msg, name=name)

If you change your module name like Erik says, then step through this whole process, you can see the call to get parent_module.__path__ works like it's supposed to and everything's happy.

So, tldr: you've tricked the import system into thinking it's already loaded your custom module, but when it goes to try and use it like a custom module it fails because it's actually the builtin io.

EDIT: It looks like __path__ is set here after it goes through a normal import process in init_module_attrs:

 if _override or getattr(module, '__path__', None) is None:
     if spec.submodule_search_locations is not None:
         try:
             module.__path__ = spec.submodule_search_locations
         except AttributeError:
             pass