Skip to main content

More Imports! - Delayed importing

A few methods to make late importing cleaner

Branch Status
master Build Status
dev Build Status

Problem

Splitting code into modules is nice, but it can result in cyclic dependencies.

foos.py

from bars import bar

def foo():
    bar()

bars.py

from foos import foo

def bar():
    foo()

We are not concerned with the infinite recursion; this is only for demonstrating cyclic dependencies.

More Imports!

Solution #1: Use expect/export pattern

All your cyclic dependencies are covered with this one pattern: Break cycles by expecting a name in the first module, and let the second module export to the first when the value is available

foos.py

from mo_imports import expect

bar = expect("bar")

def foo():
    bar()

bars.py

from mo_imports import export
from foos import foo

def bar():
    foo()

export("bars", bar)

Benefits

  • every expect is verified to match with an export (and visa-versa)
  • using an expected variable before export raises an error
  • code is run only once, at module load time, not later
  • methods do not run import code
  • all "imports" are at the top of the file

Solution #2: Use delay_import

Provide a proxy which is responsible for import upon first use of the module variable.

foos.py

from mo_imports import delay_import

bar = delay_import("bars.bar")

def foo():
    bar()

bars.py

from foos import foo

def bar():
    foo()

Benefits

  • cleaner code
  • costly imports are delayed until first use

WARNING

Requires any of __call__, __getitem__, __getattr__ to be called to trigger the import. This means sentinals, placeholders, and default values can NOT be imported using delay_import()

Other solutions

If you do not use mo-imports your import cycles can be broken using one of the following common patterns:

Bad Solution: Keep in single file

You can declare yet-another-module that holds the cycles

foosbars.py

    def foo():
        bar()

    def bar():
        foo()

but this breaks the code modularity

Bad Solution: Use end-of-file imports

During import, setup of the first module is paused while it imports a second. A bottom-of-file import will ensure the first module is mostly setup to be used by the second.

foos.py

def foo():
    bar()

from bars import bar

bars.py

def bar():
    foo()

from foos import foo

Linters do not like this pattern: You may miss imports, since these are hiding at the bottom.

Bad Solution: Inline import

Import the name only when it is needed

foos.py

def foo():
    from bars import bar
    bar()

bars.py

def bar():
    from foos import foo
    foo()

This is fine for rarely run code, but there is an undesirable overhead because import is checked everytime the method is run. You may miss imports because they are hiding inline rather than at the top of the file.

Bad Solution: Use the _late_import() pattern

When other bad solutions do not work work, then importing late is the remaining option

foos.py

from bars import bar

def foo():
    bar()

bars.py

foo = None

def _late_import():
    global foo
    from foos import foo
    _ = foo

def bar():
    if not foo:
        _late_import()
    foo()

Placeholders variables are added, which linters complain about type. There is the added _late_import() method. You risk it is not run everywhere as needed. This has less overhead than an inline import, but there is still a check.

More on importing

Importing a complex modular library is still hard; the complexity comes from the the order other modules declare their imports; you have no control over which of your modules will be imported first. For example, one module may

from my_lib.bars import bar
from my_lib.foos import foo

another module may choose the opposite order

from my_lib.foos import foo
from my_lib.bars import bar

Ordering imports

With cyclic dependencies, ordering the imports can get tricky. Here are some rules

  • choose your principle modules and the order you want them imported.
  • your remaining modules are assumed to be imported in alphabetical order (as most linters prefer)
  • use top level __init__.py to control the order of imports
  • encourage third party modules to use this top level module. for example
    from my_lib import foo, bar
    
  • finally, use mo_imports to break cycles

Metadata

Release files for mo-imports 7.688.26259

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for mo-imports 7.688.26259
File Size Uploaded
mo_imports-7.688.26259.tar.gz 12.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for mo-imports 7.688.26259
File Interpreter ABI Platform
mo_imports-7.688.26259-py3-none-any.whl Python 3 none any Details

Total release size: 24.1 kB

Release files / mo_imports-7.688.26259.tar.gz

Download URL mo_imports-7.688.26259.tar.gz
Size 12.4 kB
Tags Source
SHA-256 checksum
How to use checksums
5f2951fa215a4e0fe3035dd50da4346b668c6b1bf4cce4a8b92a9ab0fdc8d565
BLAKE2b-256 checksum
How to use checksums
84786969546a4128ffe5bd34102d976c6def8ee0c54bafe57d18eb208e2ec939
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.1.0 CPython/3.13.1

Release files / mo_imports-7.688.26259-py3-none-any.whl

Download URL mo_imports-7.688.26259-py3-none-any.whl
Size 11.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
92d68ba64f5597415b2dc7302640824b295b62fce791c59ee1db540f057c71ff
BLAKE2b-256 checksum
How to use checksums
33622c11d8950c074fe5e399501eb20ad4511b6abd658227fe10d78ea14f68e1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.1.0 CPython/3.13.1

Release history Release notifications | RSS feed

This release

7.688.26259 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page