Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python submodule importing correctly in python 3.7 but not 3.6

I have a directory structure as follows:

test/
  __init__.py
  m1/
    __init__.py
    f1.py
    f2.py

test/__init__.py is empty.

test/m1/__init__.py contains a single line import test.m1.f1.

test/m1/f1.py contains a single line import test.m1.f2 as f2.

In python 3.7.6, I can do import test.m1 and everything works as expected. However, in python 3.6.9 when I try this I get the following error:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/scratch/test/m1/__init__.py", line 2, in <module>
    import test.m1.f1
  File "/home/scratch/test/m1/f1.py", line 1, in <module>
    import test.m1.f2 as f2
AttributeError: module 'test' has no attribute 'm1'

This seems strange, because it does not error on the import test.m1.f1, which is the first thing it encounters. It errors on a subsequent import test.m1.f2 as f2 statement, claiming that test has no m1 submodule.

like image 640
Matt F. Avatar asked Jun 22 '20 22:06

Matt F.


People also ask

Why can't Python find my import?

This is caused by the fact that the version of Python you're running your script with is not configured to search for modules where you've installed them. This happens when you use the wrong installation of pip to install packages.

Does Python import order matter?

Import order does not matter. If a module relies on other modules, it needs to import them itself. Python treats each . py file as a self-contained unit as far as what's visible in that file.

How do I fix Python module not found?

Python's ImportError ( ModuleNotFoundError ) indicates that you tried to import a module that Python doesn't find. It can usually be eliminated by adding a file named __init__.py to the directory and then adding this directory to $PYTHONPATH .

Why can’t I import a module in Python?

Since there are no finders, Python can’t find or import new modules. However, Python can still import modules that are already in the module cache since it looks there before calling any finders. In the example above, importlib was already loaded under the hood before you cleared the list of finders.

What is the import system in Python 3?

The import system — Python 3.9.0 documentation. 5. The import system ¶. Python code in one module gains access to the code in another module by the process of importing it. The import statement is the most common way of invoking the import machinery, but it is not the only way.

What is a cyclical import in Python?

A cyclical import happens when you have two or more modules importing each other. More concretely, imagine that the module yin uses import yang and the module yang similarly imports yin. Python’s import system is to some extent designed to handle import cycles.

How do I use the import machinery in Python?

The import statement is the most common way of invoking the import machinery, but it is not the only way. Functions such as importlib.import_module () and built-in __import__ () can also be used to invoke the import machinery. The import statement combines two operations; it searches for the named module,...


2 Answers

import test.m1.f2 as f2 tries to access the m1 attribute of the test module object, as part of the process of finding the object to bind to f2. The m1 attribute won't be set until the test.m1 subpackage finishes initializing, which won't happen until the __init__.py for test.m1 finishes executing.

On Python 3.7 and up, if the attribute lookup fails, the import falls back to a sys.modules['test.m1.f2'] lookup to find test.m1.f2. This fallback does not exist on 3.6, causing the observed discrepancy.

A similar fallback also exists for circular from imports on Python 3.5 and up, though from . import f2 or from test.m1 import f2 wouldn't need the fallback. The from import retrieves test.m1 straight from sys.modules, so it only looks for the f2 attribute, and that attribute is present.

like image 122
user2357112 supports Monica Avatar answered Oct 24 '22 22:10

user2357112 supports Monica


This is not the answer explaining the how, but it explains how you can avoid the error. So useful for the ones wanting to write code that runs under 3.6 or earlier.

Replace

import test1.m1.f2 as f2

with

from test.m1 import f2

Or as @alaniwi pointed out

from . import f2
like image 1
gelonida Avatar answered Oct 25 '22 00:10

gelonida