Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Imports behave differently when in __init__.py that is imported

Imports in an __init__.py seem to behave differently when the file is run, to when it is imported.

If we have the following files:

run.py:

import test

test/b.py:

class B(object):
    pass

test/__init__.py:

from b import B
print B
print b

If we run __init__.py we get an error as I expect:

% python test/__init__.py

<class 'b.B'>
Traceback (most recent call last):
  File "test/__init__.py", line 6, in <module>
  print b
NameError: name 'b' is not defined

But if we run.py then we don't:

% python run.py 
<class 'test.b.B'>
<module 'test.b' from '~/temp/test/b.py'>

I would expect the behaviour to be the same. Why does this work?

This only works if we do it in an __init__.py. If we:

mv __init__.py a.py
touch __init__.py

and make run.py:

import test.a

Then we do get the error.

like image 950
DeTeReR Avatar asked Dec 23 '15 11:12

DeTeReR


1 Answers

The situation is the following: you have a script (run.py), a package test and its submodule test.b.

Whenever you import a submodule in Python, the name of that submodule is automatically stored into the parent package. So that when you do import collections.abc (or from collections.abc import Iterable, or similar), the package collections automatically gets the attribute abc.

This is what's happening here. When you do:

from b import B

the name b is automatically loaded into test, because b is a submodule of the test package.

Even if you don't do import b explicitly, whenever you import that module, the name is placed into test. Because b is a submodule of test, and it belongs to test.


Side node: your code won't work with Python 3, because relative imports have been removed. To make your code work with Python 3, you would have to write:

from test.b import B

This syntax is perfectly identical to from b import B, but is much more explicit, and should help you understand what's going on.


Reference: the Python reference documentation also explains this behavior, and includes a helpful example, very similar to this situation (the difference is just that an absolute import is used, instead of a relative import).

When a submodule is loaded using any mechanism (e.g. importlib APIs, the import or import-from statements, or built-in __import__()) a binding is placed in the parent module's namespace to the submodule object. For example, if package spam has a submodule foo, after importing spam.foo, spam will have an attribute foo which is bound to the submodule.

Let's say you have the following directory structure:

spam/
    __init__.py
    foo.py
    bar.py

and spam/__init__.py has the following lines in it:

from .foo import Foo
from .bar import Bar

then executing the following puts a name binding to foo and bar in the spam module:

>>> import spam
>>> spam.foo
<module 'spam.foo' from '/tmp/imports/spam/foo.py'>
>>> spam.bar
<module 'spam.bar' from '/tmp/imports/spam/bar.py'>

Given Python's familiar name binding rules this might seem surprising, but it's actually a fundamental feature of the import system. The invariant holding is that if you have sys.modules['spam'] and sys.modules['spam.foo'] (as you would after the above import), the latter must appear as the foo attribute of the former.

like image 123
Andrea Corbellini Avatar answered Oct 16 '22 23:10

Andrea Corbellini