Skip to content

Instantly share code, notes, and snippets.

@justinchuby
Last active March 21, 2024 18:46
Show Gist options
  • Save justinchuby/9085242a53158f2fd7ae7aa650e55ee3 to your computer and use it in GitHub Desktop.
Save justinchuby/9085242a53158f2fd7ae7aa650e55ee3 to your computer and use it in GitHub Desktop.
Best practice for importing in Python

Best practice for importing in Python

1. Import at top level only

Allow imports at the module toplevel only, unless (1) it is too expensive to load the module or (2) module may not be available.

  • It is clear what a module needs when all imports are grouped in a single place. This makes refactoring easy.
  • Otherwise, any import errors will be raised only when the code executes. Erroneous imports may go undetected until the code path is hit at runtime.
  • Doing so reduces code duplication and improves consistency when we don't have the same import lines spread across the file.
  • Pylint has rule that checks for this: https://pylint.readthedocs.io/en/latest/user_guide/messages/convention/import-outside-toplevel.html

2. Avoid relative imports

Even though relative imports are prevalent in many places, they are confusing, harder to manage and refactor. We should prefer absolute imports for clarity and robustness.

3. Import only modules

Import only modules (in most cases inside modules that is not __init__.py).

Import only the module instead of classes and functions to (1) keep the namespace clean and provide readers with more context on where its members come from, and (2) prevent circular import errors.

  • Prevent circular import errors: Programming FAQ — Python 3.10.4 documentation

    Circular imports are fine where both modules use the "import" form of import. They fail when the 2nd module wants to grab a name out of the first ("from module import name") and the import is at the top level. That's because names in the 1st are not yet available, because the first module is busy importing the 2nd.

  • Clean namespace: For example, readers don't need to backtrack to see sleep is a function from time, as opposed to a function defined in the file. https://google.github.io/styleguide/pyguide.html#22-imports

DO

import torch

a = torch.tensor(...)
import time

time.sleep(...)

Don't

from torch import tensor

a = tensor(...)
from time import sleep

sleep(...)

4. Handling circular imports

It is tempting to move imports into a function to avoid a circular import situation. There are better things to do.

  1. Most circular imports are caused by importing members from modules directly. E.g. from module import MyClass. This can be resolved by importing modules only (3).

    - from my_module import Foo
    + import my_module
    
    def bar():
        return Foo()
  2. If the cycle is created by using a member in type annotation, delay import the type with from __future__ import annotations

    + from __future__ import annotations
    
    import my_module
    
    def foo(a: my_module.Foo): ...
  3. If the cycle is created by inheritance in a top level class definition, the code needs to be refactored following suggestions on https://codeql.github.com/codeql-query-help/python/py-cyclic-import/.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment