简体   繁体   中英

Import from relative path in Python: strange behavior

As far as I understand from the python docs , from package import x statement should bind only x , but not package , into the current namespace. But in practice, if package is a relative name, it is also bound sometimes!

Let me provide an example. Consider the following file hierarchy:

root/
  package/
    __init__.py
    subpackage/
      __init__.py

subpackage/__init__.py:

foo = 42

package/__init__.py:

from os import name
from .subpackage import foo

print(globals().get('name'))
print(globals().get('os'))
print(globals().get('foo'))
print(globals().get('subpackage'))

Now let's run python (either v2 or v3) interpreter from the root directory and execute

>>> import package

The first three output lines are predictable:

posix
None
42

But the last one is <module 'package.subpackage' ...> rather than None , and this confuses me somewhat.

Have I missed something? Is it expected behavior? What is the reason?


The situation seems even more weird to me in this case:

root/
  __init__.py  # Empty.
  package/
    __init__.py
  another_package/
    __init__.py

another_package/__init__.py:

bar = 33

package/__init__.py:

from ..another_package import bar

print(globals().get('another_package'))

Now I run this outside the root:

>>> import root.package
None  # OK.
>>> dir(root.package)
['__builtins__', ..., '__path__', 'bar']  # OK.
>>> dir(root)
['__builtins__', ..., '__path__', 'another_package', 'package']  # What?!

Why did another_package appear in dir(root) ?

It's important to realize modules are loaded once at most (unless they are explicitly reloaded ). If a module is imported in multiple modules, the same module object is referenced by them all. Eg:

Module M.py

bar = 10

Module A.py

import M
M.bar = 4

Module B.py

import M
M.bar = 6

So:

>>> import M
>>> M.bar
10
>>> import A
>>> M.bar  # A is referencing the same M module object!!
4
>>> import B
>>> M.bar # B is referencing the same M module object!!
6

Now, when the statement from ..another_package import bar is executed it is basically equivalent to executing from root.another_package import bar . Since another_package is indeed a module inside the root package, the statement succeeds which results with the following effects (there may be more, but for this purposes let's focus on these 3):

  1. root is loaded if not previously loaded (its' __init__.py is run)
  2. bar is imported into the current namespace
  3. another_package is added as an attribute to root module object

Some developers are not completely aware of items 1 and 3.

Back to your question: Let's see what happens when import root.package is executed, in this order:

  1. root 's __init__.py is run (because root wasn't loaded yet)
  2. package 's __init__.py is run (because package wasn't loaded yet)
  3. from ..another_package import bar is executed which has the side effects mentioned above, most notably, the (Yes. THE object. There is only one per each module, remember?) module object of root has the attribute another_package added to it.

This explains why another_package appears in root 's dir .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM