[](https://github.com/bachorp/lazy-imports/actions/workflows/static_checks.yml)
[](https://www.python.org)
[](https://pypi.org/project/lazy-imports/)
This python utility package helps to create *lazy modules*.
A lazy module defers loading (some of) its attributes until these attributes are first accessed.
The module's lazy attributes in turn are attributes of other modules.
These other modules will be imported/loaded only when (and if) associated attributes are used.
A lazy import strategy can drastically reduce runtime and memory consumption.
Additionally, this package provides a utility for *optional imports* with which one can import a module globally while triggering associated import errors only at use-sites (when and if a dependency is actually required, for example in the context of a specific functionality).
[`lazy-imports`](https://pypi.org/project/lazy-imports/) is available on [the Python Package Index (PyPI)](https://pypi.org/project/lazy-imports/).
> [!WARNING]
> Python's import system is highly complex and side effects are ubiquitous.
> Although employing lazy imports (in a sanely structured project) is quite safe, you should keep in mind that there are necessarily subtle differences between lazy and ordinary (eager) imports/modules.
> [!TIP]
> Using a dedicated package such as this one means that you don't have to go through all the details yourself.
> Still, we recommend to become acquainted with the basic functionality of lazy modules (such as understanding the roles of `__getattr__`, `__dir__`, and `__all__`).
> If you'd like to talk about it, feel free to open the [discussion](https://github.com/bachorp/lazy-imports/discussions) on Github.
## [`LazyModule`](./lazy_imports/lazy_module.py)
### Example 1
Type checkers cannot reason about dynamic attributes.
Therefore, we need a separate code path (using [`typing.TYPE_CHECKING`](https://docs.python.org/3/library/typing.html#typing.TYPE_CHECKING)) on which regular imports are used.
By moving the imports to a separate file and using the `module_source` utility, code duplication can be avoided.
```python
# _exports.py
from absolute.package import Class, function
from .submodule import Interface as MyInterface
```
> [!IMPORTANT]
> Lazy attributes have to be proper attributes (defined in `__init__.py`) of the module they are imported from and cannot be submodules.
> In this example, if `function` is a submodule of `absolute.package`, the lazy module won't (try to) load `absolute.package.function` as a module by itself; in this case, an access of the lazy module's attribute `function` might fail.
```python
# __init__.py
"""This is my great package."""
from typing import TYPE_CHECKING
from lazy_imports import LazyModule, as_package, load, module_source
__version__ = "0.1.0"
if TYPE_CHECKING:
from ._exports import *
else:
# Registers the lazy module in `sys.modules`
load(
# The constructor takes arbitrarily many attribute declarations in various forms
LazyModule(
# Attributes `__file__` and `__path__` (required for importing submodules of this module)
*as_package(__file__),
# An ordinary (eager) attribute
("__version__", __version__),
# Finds and parses the source of the submodule `_exports` (namely the file `_exports.py`)
module_source("._exports", __name__),
# Fully-qualified name of the module (required)
name=__name__,
# Docstring from the top of the module
doc=__doc__,
)
)
```
### Example 2
```python
# __init__.py
import ast as _ast
from typing import TYPE_CHECKING
from lazy_imports import LazyModule as _LazyModule
# Keep outside the `not TYPE_CHECKING` branch such that your type checker does not skip this expression
_mod = LazyModule(
*as_package(__file__),
# A string with arbitrarily many `from <module> import <attribute>`-statements
"from . import echo",
# Plain type from `ast`
ast.ImportFrom(names=[ast.alias(name="formats")], level=2),
ast.ImportFrom(module="filters", names=[ast.alias(name="equalizer", asname="eq")], level=2),
name=__name__,
)
if TYPE_CHECKING:
from . import echo
from .. import formats
from ..filters import equalizer as eq
else:
# NOTE: If you use this trick (instead of directly writing to `sys.modules`), you'll have to make
# sure that none of the attributes overlap with already defined variables (such as `_ast`).
__getattr__, __dir__, __all__ = _mod.__getattr__, _mod.__dir__, _mod.__all__
```
## [`try_import`](./lazy_imports/try_import.py)
`try_import` is a context manager that can wrap imports of optional packages to defer exceptions.
This way you don't have to import the packages every time you call a function, but you can still import the package at the top of your module.
The context manager defers the exceptions until you actually need to use the package.
You can see an example below.
```python
from lazy_imports import try_import
with try_import() as optional_package_import: # use `try_import` as a context manager
import optional_package # optional package that might not be installed
def optional_function(): # optional function that uses the optional package
optional_package_import.check() # check if the import was ok or raise a meaningful exception
optional_package.some_external_function() # use the optional package here
```
## [`LazyImporter`](./lazy_imports/v0/lazy_imports.py)
> [!TIP]
> Instead of `LazyImporter` we recommend using its successor `LazyModule`, which
>
> - allows attributes to be imported from any module (and not just submodules),
> - offers to specify imports as plain python code (which can then be sourced from a dedicated file),
> - supports `__doc__`, and
> - applies additional sanity checks (such as preventing cyclic imports).
Usage example taken from [`hpoflow/__init__.py`](https://github.com/telekom/HPOflow/blob/1b26f3b86cad607dd89a31fa9135256d956948cb/hpoflow/__init__.py):
```python
import sys
from typing import TYPE_CHECKING
from lazy_imports import LazyImporter
from hpoflow.version import __version__
_import_structure = {
"mlflow": [
"normalize_mlflow_entry_name",
"normalize_mlflow_entry_names_in_dict",
"check_repo_is_dirty",
],
"optuna": ["SignificanceRepeatedTrainingPruner"],
"optuna_mlflow": ["OptunaMLflow"],
"optuna_transformers": ["OptunaMLflowCallback"],
"utils": ["func_no_exception_caller"],
}
# Direct imports for type-checking
if TYPE_CHECKING:
from hpoflow.mlflow import ( # noqa: F401
check_repo_is_dirty,
normalize_mlflow_entry_name,
normalize_mlflow_entry_names_in_dict,
)
from hpoflow.optuna import SignificanceRepeatedTrainingPruner # noqa: F401
from hpoflow.optuna_mlflow import OptunaMLflow # noqa: F401
from hpoflow.optuna_transformers import OptunaMLflowCallback # noqa: F401
from hpoflow.utils import func_no_exception_caller # noqa: F401
else:
sys.modules[__name__] = LazyImporter(
__name__,
globals()["__file__"],
_import_structure,
extra_objects={"__version__": __version__},
)
```
## History
This project has previously been maintained by the [One Conversation](https://welove.ai/) team of [Deutsche Telekom AG](https://www.telekom.com/).
It is based on [`_LazyModule`](https://github.com/huggingface/transformers/blob/e218249b02465ec8b6029f201f2503b9e3b61feb/src/transformers/file_utils.py#L1945) from [HuggingFace](https://huggingface.co/) and [`try_import()`](https://github.com/optuna/optuna/blob/1f92d496b0c4656645384e31539e4ee74992ff55/optuna/_imports.py#L89) from the [Optuna framework](https://optuna.readthedocs.io/).
Many thanks to HuggingFace for [your consent](https://github.com/huggingface/transformers/issues/12861#issuecomment-886712209)
and to Optuna for [your consent](https://github.com/optuna/optuna/issues/2776#issuecomment-874614137) to publish it as a standalone package 🤗 ♥.
In December 2024 responsibility was transferred to [Pascal Bachor](https://github.com/bachorp).
## Licensing
Copyright (c) 2024-2025 Pascal Bachor<br/>
Copyright (c) 2021 [Philip May](https://philipmay.org/), [Deutsche Telekom AG](https://www.telekom.com/)<br/>
Copyright (c) 2020, 2021 [The HuggingFace Team](https://huggingface.co/)<br/>
Copyright (c) 2018 Preferred Networks, Inc.
Licensed under the [Apache License, Version 2.0](https://github.com/bachorp/lazy-imports/blob/HEAD/LICENSE) (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0).
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and limitations under the License.
Raw data
{
"_id": null,
"home_page": null,
"name": "lazy-imports",
"maintainer": "Pascal Bachor",
"docs_url": null,
"requires_python": ">=3.9",
"maintainer_email": null,
"keywords": "import imports lazy",
"author": "Pascal Bachor",
"author_email": "lazy-imports.vista851@passmail.net",
"download_url": "https://files.pythonhosted.org/packages/e2/1c/6675601691fd6007021e8e95cfcb83e6d11067de3d7ecde0d0de970324c8/lazy_imports-1.0.1.tar.gz",
"platform": null,
"description": "[](https://github.com/bachorp/lazy-imports/actions/workflows/static_checks.yml)\n[](https://www.python.org)\n[](https://pypi.org/project/lazy-imports/)\n\nThis python utility package helps to create *lazy modules*.\nA lazy module defers loading (some of) its attributes until these attributes are first accessed.\nThe module's lazy attributes in turn are attributes of other modules.\nThese other modules will be imported/loaded only when (and if) associated attributes are used.\nA lazy import strategy can drastically reduce runtime and memory consumption.\n\nAdditionally, this package provides a utility for *optional imports* with which one can import a module globally while triggering associated import errors only at use-sites (when and if a dependency is actually required, for example in the context of a specific functionality).\n\n[`lazy-imports`](https://pypi.org/project/lazy-imports/) is available on [the Python Package Index (PyPI)](https://pypi.org/project/lazy-imports/).\n\n> [!WARNING]\n> Python's import system is highly complex and side effects are ubiquitous.\n> Although employing lazy imports (in a sanely structured project) is quite safe, you should keep in mind that there are necessarily subtle differences between lazy and ordinary (eager) imports/modules.\n\n> [!TIP]\n> Using a dedicated package such as this one means that you don't have to go through all the details yourself.\n> Still, we recommend to become acquainted with the basic functionality of lazy modules (such as understanding the roles of `__getattr__`, `__dir__`, and `__all__`).\n> If you'd like to talk about it, feel free to open the [discussion](https://github.com/bachorp/lazy-imports/discussions) on Github.\n\n## [`LazyModule`](./lazy_imports/lazy_module.py)\n\n### Example 1\n\nType checkers cannot reason about dynamic attributes.\nTherefore, we need a separate code path (using [`typing.TYPE_CHECKING`](https://docs.python.org/3/library/typing.html#typing.TYPE_CHECKING)) on which regular imports are used.\nBy moving the imports to a separate file and using the `module_source` utility, code duplication can be avoided.\n\n```python\n# _exports.py\nfrom absolute.package import Class, function\n\nfrom .submodule import Interface as MyInterface\n```\n\n> [!IMPORTANT]\n> Lazy attributes have to be proper attributes (defined in `__init__.py`) of the module they are imported from and cannot be submodules.\n> In this example, if `function` is a submodule of `absolute.package`, the lazy module won't (try to) load `absolute.package.function` as a module by itself; in this case, an access of the lazy module's attribute `function` might fail.\n\n```python\n# __init__.py\n\"\"\"This is my great package.\"\"\"\n\nfrom typing import TYPE_CHECKING\nfrom lazy_imports import LazyModule, as_package, load, module_source\n\n__version__ = \"0.1.0\"\n\nif TYPE_CHECKING:\n from ._exports import *\nelse:\n # Registers the lazy module in `sys.modules`\n load(\n # The constructor takes arbitrarily many attribute declarations in various forms\n LazyModule(\n # Attributes `__file__` and `__path__` (required for importing submodules of this module)\n *as_package(__file__),\n # An ordinary (eager) attribute\n (\"__version__\", __version__),\n # Finds and parses the source of the submodule `_exports` (namely the file `_exports.py`)\n module_source(\"._exports\", __name__),\n # Fully-qualified name of the module (required)\n name=__name__,\n # Docstring from the top of the module\n doc=__doc__,\n )\n )\n```\n\n### Example 2\n\n```python\n# __init__.py\nimport ast as _ast\nfrom typing import TYPE_CHECKING\nfrom lazy_imports import LazyModule as _LazyModule\n\n# Keep outside the `not TYPE_CHECKING` branch such that your type checker does not skip this expression\n_mod = LazyModule(\n *as_package(__file__),\n # A string with arbitrarily many `from <module> import <attribute>`-statements\n \"from . import echo\",\n # Plain type from `ast`\n ast.ImportFrom(names=[ast.alias(name=\"formats\")], level=2),\n ast.ImportFrom(module=\"filters\", names=[ast.alias(name=\"equalizer\", asname=\"eq\")], level=2),\n name=__name__,\n)\n\nif TYPE_CHECKING:\n from . import echo\n from .. import formats\n from ..filters import equalizer as eq\nelse:\n # NOTE: If you use this trick (instead of directly writing to `sys.modules`), you'll have to make\n # sure that none of the attributes overlap with already defined variables (such as `_ast`).\n __getattr__, __dir__, __all__ = _mod.__getattr__, _mod.__dir__, _mod.__all__\n```\n\n## [`try_import`](./lazy_imports/try_import.py)\n\n`try_import` is a context manager that can wrap imports of optional packages to defer exceptions.\nThis way you don't have to import the packages every time you call a function, but you can still import the package at the top of your module.\nThe context manager defers the exceptions until you actually need to use the package.\nYou can see an example below.\n\n```python\nfrom lazy_imports import try_import\n\nwith try_import() as optional_package_import: # use `try_import` as a context manager\n import optional_package # optional package that might not be installed\n\ndef optional_function(): # optional function that uses the optional package\n optional_package_import.check() # check if the import was ok or raise a meaningful exception\n optional_package.some_external_function() # use the optional package here\n```\n\n## [`LazyImporter`](./lazy_imports/v0/lazy_imports.py)\n\n> [!TIP]\n> Instead of `LazyImporter` we recommend using its successor `LazyModule`, which\n>\n> - allows attributes to be imported from any module (and not just submodules),\n> - offers to specify imports as plain python code (which can then be sourced from a dedicated file),\n> - supports `__doc__`, and\n> - applies additional sanity checks (such as preventing cyclic imports).\n\nUsage example taken from [`hpoflow/__init__.py`](https://github.com/telekom/HPOflow/blob/1b26f3b86cad607dd89a31fa9135256d956948cb/hpoflow/__init__.py):\n\n```python\nimport sys\nfrom typing import TYPE_CHECKING\n\nfrom lazy_imports import LazyImporter\n\nfrom hpoflow.version import __version__\n\n\n_import_structure = {\n \"mlflow\": [\n \"normalize_mlflow_entry_name\",\n \"normalize_mlflow_entry_names_in_dict\",\n \"check_repo_is_dirty\",\n ],\n \"optuna\": [\"SignificanceRepeatedTrainingPruner\"],\n \"optuna_mlflow\": [\"OptunaMLflow\"],\n \"optuna_transformers\": [\"OptunaMLflowCallback\"],\n \"utils\": [\"func_no_exception_caller\"],\n}\n\n# Direct imports for type-checking\nif TYPE_CHECKING:\n from hpoflow.mlflow import ( # noqa: F401\n check_repo_is_dirty,\n normalize_mlflow_entry_name,\n normalize_mlflow_entry_names_in_dict,\n )\n from hpoflow.optuna import SignificanceRepeatedTrainingPruner # noqa: F401\n from hpoflow.optuna_mlflow import OptunaMLflow # noqa: F401\n from hpoflow.optuna_transformers import OptunaMLflowCallback # noqa: F401\n from hpoflow.utils import func_no_exception_caller # noqa: F401\nelse:\n sys.modules[__name__] = LazyImporter(\n __name__,\n globals()[\"__file__\"],\n _import_structure,\n extra_objects={\"__version__\": __version__},\n )\n```\n\n## History\n\nThis project has previously been maintained by the [One Conversation](https://welove.ai/) team of [Deutsche Telekom AG](https://www.telekom.com/).\nIt is based on [`_LazyModule`](https://github.com/huggingface/transformers/blob/e218249b02465ec8b6029f201f2503b9e3b61feb/src/transformers/file_utils.py#L1945) from [HuggingFace](https://huggingface.co/) and [`try_import()`](https://github.com/optuna/optuna/blob/1f92d496b0c4656645384e31539e4ee74992ff55/optuna/_imports.py#L89) from the [Optuna framework](https://optuna.readthedocs.io/).\nMany thanks to HuggingFace for [your consent](https://github.com/huggingface/transformers/issues/12861#issuecomment-886712209)\nand to Optuna for [your consent](https://github.com/optuna/optuna/issues/2776#issuecomment-874614137) to publish it as a standalone package \ud83e\udd17 \u2665.\n\nIn December 2024 responsibility was transferred to [Pascal Bachor](https://github.com/bachorp).\n\n## Licensing\n\nCopyright (c) 2024-2025 Pascal Bachor<br/>\nCopyright (c) 2021 [Philip May](https://philipmay.org/), [Deutsche Telekom AG](https://www.telekom.com/)<br/>\nCopyright (c) 2020, 2021 [The HuggingFace Team](https://huggingface.co/)<br/>\nCopyright (c) 2018 Preferred Networks, Inc.\n\nLicensed under the [Apache License, Version 2.0](https://github.com/bachorp/lazy-imports/blob/HEAD/LICENSE) (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0).\nUnless required by applicable law or agreed to in writing, software distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and limitations under the License.\n",
"bugtrack_url": null,
"license": "Apache-2.0",
"summary": "Tool to support lazy imports",
"version": "1.0.1",
"project_urls": {
"Bug Tracker": "https://github.com/bachorp/lazy-imports/issues",
"Changelog": "https://github.com/bachorp/lazy-imports/blob/HEAD/CHANGELOG.md",
"Source Code": "https://github.com/bachorp/lazy-imports"
},
"split_keywords": [
"import",
"imports",
"lazy"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "8837c84d7fec58dec38564574dec8f94bb1db788598fe397f116a9d6a86d3055",
"md5": "6e1ea2334ea2d4ea1e8beffb5b997ded",
"sha256": "eb5accc33bf9987e5197e79476bbeb960b74a2c16619bdf41281b3240f730846"
},
"downloads": -1,
"filename": "lazy_imports-1.0.1-py3-none-any.whl",
"has_sig": false,
"md5_digest": "6e1ea2334ea2d4ea1e8beffb5b997ded",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.9",
"size": 18896,
"upload_time": "2025-08-09T07:15:53",
"upload_time_iso_8601": "2025-08-09T07:15:53.700336Z",
"url": "https://files.pythonhosted.org/packages/88/37/c84d7fec58dec38564574dec8f94bb1db788598fe397f116a9d6a86d3055/lazy_imports-1.0.1-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "e21c6675601691fd6007021e8e95cfcb83e6d11067de3d7ecde0d0de970324c8",
"md5": "40e92b1504b1be18a3ef868313d6339f",
"sha256": "7d3e4b1547cb574ec7ef3c47a074673e2612330b2b50bf7eec939f2c393fc261"
},
"downloads": -1,
"filename": "lazy_imports-1.0.1.tar.gz",
"has_sig": false,
"md5_digest": "40e92b1504b1be18a3ef868313d6339f",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.9",
"size": 24484,
"upload_time": "2025-08-09T07:15:55",
"upload_time_iso_8601": "2025-08-09T07:15:55.353755Z",
"url": "https://files.pythonhosted.org/packages/e2/1c/6675601691fd6007021e8e95cfcb83e6d11067de3d7ecde0d0de970324c8/lazy_imports-1.0.1.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-08-09 07:15:55",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "bachorp",
"github_project": "lazy-imports",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "lazy-imports"
}