Differences of "import ... as" between Python 3.6 and Python 3.7

Hi folks,

I have noticed some subtle differences in the behavior of import ... as between Python 3.6 and Python 3.7 but I failed to find the explanation in the documentation and StackOverflow. I found that sometimes import .... as works only for Python 3.7, not for Python 3.6. However, from ... import works for both.

More specifically, I have the following directory structure:

$ tree kkk/
kkk/
├── __init__.py
└── testing
    ├── __init__.py
    └── ks
        ├── __init__.py
        └── r.py

And the file contents are as follows:

  • kkk/init.py:
  • kkk/testing/init.py:
import kkk.testing.ks.r as r
  • kkk/testing/ks/init.py:
from .r import *
  • kkk/testing/ks/r.py:
def f():
    pass

When import kkk.testing from Python 3.6, there would be an exception, however, Python 3.7 works well:

>>> import kkk.testing
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/tmp/kkk/testing/__init__.py", line 1, in <module>
    import kkk.testing.ks.r as r
AttributeError: module 'kkk' has no attribute 'testing'

If I change the content in kkk/testing/__init__.py as follows, both Python 3.6 and Python 3.7 works as expected:

from kkk.testing.ks import r

I have tried to search for what changed between Python 3.6 and Python 3.7 but failed to find related information in the changelog. Could somebody tell me what actually changed and what happens under the hood with Python 3.6?

Thanks!

1 Like

What’s new in Python 3.7 mentions “absolute imports with binding a submodule to a name” pointing to this:

Maybe that’s it?

1 Like

Thanks for the link!

Confirmed that the 1-line change in https://github.com/python/cpython/issues/74210 (bpo-30024: Circular imports involving absolute imports with binding by serhiy-storchaka · Pull Request #1264 · python/cpython · GitHub) leads to the differences.

Learned a lot from this!

1 Like