More Imports! - Delayed importing
mo-imports is a Python library designed to simplify and manage complex import patterns, especially those involving cyclic dependencies. It provides mechanisms like 'expect/export' and 'delay_import' to make late importing cleaner and avoid common Python import pitfalls. The library is currently in a beta development status, with version 7.685.25166 released on June 15, 2025, and generally follows a frequent release schedule.
Common errors
-
NameError: name 'my_variable' is not defined
cause You are using a variable declared with `mo_imports.expect('my_variable')` in one module, but the module responsible for `mo_imports.export('my_variable', value)` has not yet been imported or executed when 'my_variable' is accessed.fixReview your module import order. Ensure that the module exporting the variable is imported before the module that expects it. The `expect` call sets up a placeholder, but the actual value is only bound upon `export`. -
AttributeError: 'DelayImport' object has no attribute 'some_attribute'
cause You are attempting to access an attribute or call a method on a `delay_import` proxy object, but the underlying module has not yet been loaded because no triggering operation (call, item access, attribute access) has occurred.fixEnsure that the `delay_import` proxy is interacted with in a way that triggers the actual import, such as calling it (`obj()`) if it's a function, accessing an item (`obj['key']`) if it's a dictionary-like object, or accessing an attribute (`obj.attr`) if it's a module/class. If the object itself is meant to be a simple value, `delay_import` might not be the appropriate tool.
Warnings
- gotcha The `delay_import` function only triggers the actual import when the proxy object's `__call__`, `__getitem__`, or `__getattr__` methods are invoked. This means that direct use of the proxy as a sentinel, placeholder, or default value without one of these operations will NOT trigger the import, leading to unexpected behavior.
- deprecated The library explicitly discourages 'Bad Solution' patterns for handling imports, such as 'end-of-file imports', 'inline imports', and the '_late_import()' pattern. These methods often lead to linter warnings, missed imports, increased overhead, or reduced code readability.
- gotcha When using the `expect`/`export` pattern, attempting to use an `expect`-ed variable before its corresponding `export` has been executed will result in a `NameError` or similar runtime error, as the variable will not yet be bound.
Install
-
pip install mo-imports
Imports
- expect
from mo_imports import expect
- export
from mo_imports import export
- delay_import
from mo_imports import delay_import
Quickstart
import os
# Example for breaking cyclic dependencies with expect/export
# File: foos.py
# from mo_imports import expect
# bar = expect("bar")
# def foo():
# return bar() + " from foo"
#
# File: bars.py
# from mo_imports import export
# from foos import foo
# def bar():
# return foo() + " from bar"
# export("bar", bar)
# Simulating the usage (actual usage requires separate files)
# To demonstrate, imagine 'foos.py' defines 'bar' as expect('bar')
# and 'bars.py' defines 'bar' and then export('bar', bar)
# Example for delayed import
# File: lazy_module.py
# def some_function():
# return "Function from lazy_module"
# File: main_app.py
from mo_imports import delay_import
# Instead of: from lazy_module import some_function
# We use delay_import:
lazy_function = delay_import("lazy_module.some_function")
print(f"Initial state: {lazy_function}") # This is a proxy object, not the actual function yet
# The import happens when lazy_function is first 'used'
# For a function, this means when it's called.
try:
result = lazy_function()
print(f"Result from delayed import: {result}")
except ImportError as e:
print(f"Caught expected ImportError: {e}")
print("Note: For this example to run correctly, 'lazy_module.py' must exist in the Python path.")
# For demonstration, let's create a dummy lazy_module.py temporarily
with open('lazy_module.py', 'w') as f:
f.write('def some_function():\n return "Function from lazy_module"\n')
# Rerun the delayed import to show it working
import sys
# Remove from sys.modules to force re-import if it was loaded before
if 'lazy_module' in sys.modules:
del sys.modules['lazy_module']
lazy_function_working = delay_import("lazy_module.some_function")
print(f"Result from delayed import after creating file: {lazy_function_working()}")
# Clean up the dummy file
os.remove('lazy_module.py')