Search code examples
pythonpython-importrelative-import

Relative Import --- Ultimate Version


|-- eval.py
|-- modeling_llama.py
|-- patch
|   |-- __init__.py
|   |-- flash_attn.py
|   |-- log_scale.py
|   |-- ntk.py
|   |-- ntk_fixed.py
|   |-- ntk_mixed.py
|   `-- rerope.py
|-- prompt.py
|-- test_model.sh
`-- utils.py

Above is my project directory.

The script I run is eval.py, which imports utils.py. patch is imported to utils.py. patch.rerope depends on modelling_llama, so I have to import modelling_llama in rerope. I'm confused about how I can correctly import modelling_llama in rerope since it's a repository level higher.

Should I be import modelling_llama, from .. import modelling_llama, or import ..modeling_llama.

I've tried all the above and found merely import modelling_llama work fine for me, but I'm still wondering why.


Solution

  • I went through several questions regarding relative import or python-import, all of them provided useful information, but none of them gave me a clear whole picture of how to use python-import.

    After reading Python documentation and related PEP, and doing some necessary experiments, I think I've got a clear picture of this issue. While I may still miss some corner cases, I share my findings here and hope it's useful.

    Import convention

    Before we dive into details, let's first classify python-import.

    All import statements are absolute import by default.

    Only special syntax (leading dots, e.g. from ..modelling_llama import module_x) leads to relative imports.

    Absolute Imports

    Absolute imports search from sys.path.

    Which is initialized upon program startup, a potentially unsafe path is prepended to it:

    • python -m module command line: prepend the current working directory.
    • python script.py command line: prepend the script’s directory. If it’s a symbolic link, resolve symbolic links.
    • python -c code and python (REPL) command lines: prepend an empty string, which means the current working directory.

    This paragraph is from Python Documentation, but it only talks about how sys.path is initialized. But how will sys.path change for each imported file? After simple experiments, I've come to the conclusion that except for the main script whose sys.path is initialized the way above, all the subsequent imported modules will inherit this sys.path and only look for packages from this path.

    This perfectly answers my question of why merely import modelling_llama works well.

    Relative Imports

    Relative imports use a module’s __name__ attribute to determine that module’s position in the package hierarchy.

    For how to determine the __name__ attribute, Relative imports for the billionth time is quite clear.

    In short:

    • If it was loaded as the top-level script, its name is __main__.
    • If it was loaded as a module, its name is [the filename, preceded by the names of any packages/subpackages of which it is a part, separated by dots], for example, package.subpackage1.moduleX.

    This again perfectly explains why from ..modeling_llama import module_x will fail. Since the name of this rerope module is patch.rerope, .. will lead to beyond top level package error in relative import, which makes sense.

    So what if I insisted on using relative imports? Adding another folder level to match .. should work fine!