Python's import
mechanism (commonly referred as the import machinery) is based on Finders and Loaders. When you import a module using the import statement, a series are finders try to find the module that you are importing. You can see the list of all finders that are triggered in order by:
>>> import sys
>>> sys.meta_path
[<class '_frozen_importlib.BuiltinImporter'>,
<class '_frozen_importlib.FrozenImporter'>,
<class '_frozen_importlib_external.PathFinder'>]
If none of the finders are able to resolve the module, then ModuleNotFoundError
is raised.
BuiltinImporter
resolves modules like sys, time, gc etc which are preloaded into the interpreter. FrozenImporter
resolves frozen modules. PathFinder
resolves modules by going through the paths in sys.path
.
Printing sys.path
shows that your current directory is the first entry and hence is searched for a match first.
Here's a simple sequence you can use to reason about where the import is going to happen from:
- Is the module part of built-in modules. Example:
import sys
. Even if you have a file named sys.py in your current directory, since BuiltinImporter
is called first, it will resolve the built-in sys module.
- Is the module present in your current directory, since that is the first entry in
sys.path
.
- Is the module available as part of core python or site-packages.
Modules that are imported by PathFinder (ones present in sys.path) will have __path__
as an attribute that you can check to see where it is located.
It is also possible to reorder the finders in sys.meta_path or even add your own custom finders and loaders giving you full control and creativity to modify the default machinery. Custom finders and loaders can be registered to look for modules elsewhere in the system or even dynamically from external systems.