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.

Matt F.
  • 63
  • 4
  • Please can you give the contents of `test/__init__.py`, or if it is empty, say so explicitly. – alani Jun 22 '20 at 23:20
  • I can reproduce it with `test/__init__.py` being empty version 3.6.9 fails. version 3.7.4 passes. replacing `import test.m1.f2 as f2` with `import test.m1.f2` makes it pass. – gelonida Jun 22 '20 at 23:22
  • If it's any help, it will work under python 3.6 if you change `test/m1/f1.py` to say `from . import f2` – alani Jun 22 '20 at 23:29

2 Answers2

6

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.

user2357112
  • 260,549
  • 28
  • 431
  • 505
1

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
gelonida
  • 5,327
  • 2
  • 23
  • 41
  • This works on Python 3.5 and up, but not prior versions. – user2357112 Jun 22 '20 at 23:26
  • For me that works with following versions 2.6.9 2.7.15 3.4.5 3.5.4 3.6.9 3.7.4 3.8.1 or in other words I didn't find any version where it doesn't work, but of course I didn't try all versions. Perhaps you mean 2.5 and higher versions? – gelonida Jun 22 '20 at 23:30
  • Is that better or worse than using a relative import (`from . import f2`)? (And why?) – alani Jun 22 '20 at 23:31
  • Oh, whoops, you're right. I was thinking of a slightly different case. This import doesn't need the fallback introduced in 3.5, since `f1` and `f2` don't both try to import each other. – user2357112 Jun 22 '20 at 23:33
  • Ah I see. Circular imports are another painful scenario. this one is just about when the name spaces are populated. – gelonida Jun 22 '20 at 23:34
  • 1
    @alaniwi This might give you opinionated answers. Explicit relative imports didn't exist in python2 and some coding style checkers issue warnings for relative imports (at least a few years ago pylint did this if I recall correctly) I think both are fine. – gelonida Jun 22 '20 at 23:38
  • 1
    Explicit relative imports work fine in Python 2. Python 2's problem is *implicit* relative imports. – user2357112 Jun 22 '20 at 23:39
  • You're right. I mixed it up. With python2 you even get implicit relative imports where you probably wanted an absolute import (except you add `from __future__ import absolute_imports`) – gelonida Jun 22 '20 at 23:43