Python import precedence: packages or modules?
Asked Answered
H

5

23

I wasn't clear how to correctly name this question.

Case 1

Assume that I have the following directory structure.

foo
|
+- bar/__init__.py
|
+- bar.py

If I have

from foo import bar

How do I know which bar (bar.py or bar/__init__.py) is being imported? Is there any easy way to automatically detect this from occurring?

Case 2

foo
|
+- foo.py
|
+- other.py

If other.py has the line

import foo

How do I know which foo (foo or foo.foo) is being imported? Again, is tehre any easy way to automatically detect this from occurring?

Henkel answered 3/11, 2010 at 22:56 Comment(4)
Easy way to detect it: have bar.py's first line be print("bar.py imported")Pelias
I should have RTFM-ed. docs.python.org/tutorial/modules.html is a great resource.Henkel
Is there a way to force the program to use one interpretation or the other?Crucifix
https://mcmap.net/q/586275/-what-does-quot-import-quot-prefer-pyd-so-or-py/674039Deathbed
P
11

TLDR; a package takes precedence over a module of the same name if they are in the same directory.

From the docs:

"When a module named spam is imported, the interpreter searches for a file named spam.py in the current directory, and then in the list of directories specified by the environment variable PYTHONPATH. This has the same syntax as the shell variable PATH, that is, a list of directory names."

This is a bit misleading because the interpreter will also look for a package called spam (a directory called spam containing an __init__.py file). Since the directory entries are sorted before searching, packages take precedence over modules with the same name if they are in the same directory because spam comes before spam.py.

Note that "current directory" is relative to the main script path (the one where __name__ == '__main__' is True). So if you are at /home/billg calling /foo/bar.py, "current directory" refers to /foo.

Popular answered 3/11, 2010 at 23:4 Comment(3)
Have things changed? because I don't seem to get the current directory added as you suggest. The docs now state, "When a module named spam is imported, the interpreter first searches for a built-in module with that name. If not found, it then searches for a file named spam.py in a list of directories given by the variable sys.path." And I don't see that the "current directory" is listed. But since I'm lost maybe I'm misunderstanding?Spinal
And (I could be misunderstanding all of this) a previous stackoverflow answer states, "Python does not add the current directory to sys.path, but rather the directory that the script is in." #2326423Spinal
This doesn't seem to answer Case 1Orgiastic
H
5

from a python shell:

from foo import bar

print bar.__file__

should tell you which file has been imported

Rob

Hoxha answered 3/11, 2010 at 23:33 Comment(2)
has not attribute file on python3Blockbusting
This does not mean that the behaviour is specified somewhere.Hilde
B
5

I would like to complement the accepted answer. For Python 3.3+, namespace packages have been introduced and the import order according to PEP 420 follows:

During import processing, the import machinery will continue to iterate over each directory in the parent path as it does in Python 3.2. While looking for a module or package named "foo", for each directory in the parent path:

  • If <directory>/foo/__init__.py is found, a regular package is imported and returned.
  • If not, but <directory>/foo.{py,pyc,so,pyd} is found, a module is imported and returned. The exact list of extension varies by platform and whether the -O flag is specified. The list here is representative.
  • If not, but <directory>/foo is found and is a directory, it is recorded and the scan continues with the next directory in the parent path.
  • Otherwise the scan continues with the next directory in the parent path.

If the scan completes without returning a module or package, and at least one directory was recorded, then a namespace package is created.

Britishism answered 8/11, 2020 at 13:11 Comment(0)
M
1

Packages (directories with __init__.py) take precedence over modules. The documentation of this fact is difficult to find but you can see this in the source: python 2.7, python 3.6 (thanks @qff for the find).

You will also need a __init__.py within the foo directory for your example to work.

If other.py is inside of foo/ then it will load foo.py (not the directory foo/) because it will look in the current directory first (unless you've played with PYTHONPATH or sys.path).

Macao answered 3/11, 2010 at 23:1 Comment(3)
How do you know it takes precedence? – I couldn't find it in the Python documentationOrgiastic
@Orgiastic I tested it. I also tested python3 just now and it has the same behavior. A link to official documentation of the fact would be good. If you find one, please feel free to edit my answer or post a comment and I'll edit it.Macao
Found it! (kind of) – the entries of a directory are sorted before trying to load each one as either a package or module. This ensures packages are loaded first. Link to CPython source codeOrgiastic
B
-1

in the first case you're trying to import the function bar from file 'foo.py'

In the second you're trying to import the file 'foo.py'

Bildungsroman answered 3/11, 2010 at 22:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.