# More Imports! - Delayed importing
A few methods to make late importing cleaner
|Branch |Status |
|------------|---------|
|master | [](https://github.com/klahnakoski/mo-imports/actions/workflows/build.yml) |
|dev | [](https://travis-ci.com/github/klahnakoski/mo-imports) |
## Problem
Splitting code into modules is nice, but it can result in cyclic dependencies.
**foos.py**
```python
from bars import bar
def foo():
bar()
```
**bars.py**
```python
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 `expect`ing a name in the first module, and let the second module `export` to the first when the value is available
**foos.py**
```python
from mo_imports import expect
bar = expect("bar")
def foo():
bar()
```
**bars.py**
```python
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**
```python
from mo_imports import delay_import
bar = delay_import("bars.bar")
def foo():
bar()
```
**bars.py**
```python
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**
```python
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**
```python
def foo():
bar()
from bars import bar
```
**bars.py**
```python
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**
```python
def foo():
from bars import bar
bar()
```
**bars.py**
```python
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**
```python
from bars import bar
def foo():
bar()
```
**bars.py**
```python
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
```python
from my_lib.bars import bar
from my_lib.foos import foo
```
another module may choose the opposite order
```python
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
```python
from my_lib import foo, bar
```
* finally, use `mo_imports` to break cycles
Raw data
{
"_id": null,
"home_page": "https://github.com/klahnakoski/mo-imports",
"name": "mo-imports",
"maintainer": null,
"docs_url": null,
"requires_python": null,
"maintainer_email": null,
"keywords": null,
"author": "Kyle Lahnakoski",
"author_email": "kyle@lahnakoski.com",
"download_url": "https://files.pythonhosted.org/packages/b8/e2/a37efda497dd57f03955ec7d210635bf02bf7e419223f64a9e375687fce5/mo_imports-7.671.25036.tar.gz",
"platform": null,
"description": "# More Imports! - Delayed importing \r\n\r\nA few methods to make late importing cleaner\r\n\r\n\r\n|Branch |Status |\r\n|------------|---------|\r\n|master | [](https://github.com/klahnakoski/mo-imports/actions/workflows/build.yml) |\r\n|dev | [](https://travis-ci.com/github/klahnakoski/mo-imports) |\r\n\r\n\r\n\r\n## Problem\r\n\r\nSplitting code into modules is nice, but it can result in cyclic dependencies. \r\n\r\n\r\n**foos.py**\r\n\r\n```python\r\nfrom bars import bar\r\n\r\ndef foo():\r\n bar()\r\n```\r\n\r\n**bars.py**\r\n\r\n```python\r\nfrom foos import foo\r\n\r\ndef bar():\r\n foo()\r\n```\r\n\r\n> We are not concerned with the infinite recursion; this is only for demonstrating cyclic dependencies. \r\n\r\n\r\n## More Imports!\r\n\r\n### Solution #1: Use `expect`/`export` pattern\r\n\r\nAll your cyclic dependencies are covered with this one pattern: Break cycles by `expect`ing a name in the first module, and let the second module `export` to the first when the value is available\r\n\r\n**foos.py**\r\n\r\n```python\r\nfrom mo_imports import expect\r\n\r\nbar = expect(\"bar\")\r\n\r\ndef foo():\r\n bar()\r\n```\r\n\r\n**bars.py**\r\n\r\n```python\r\nfrom mo_imports import export\r\nfrom foos import foo\r\n\r\ndef bar():\r\n foo()\r\n\r\nexport(\"bars\", bar)\r\n```\r\n\r\n**Benefits**\r\n \r\n \r\n* every `expect` is verified to match with an `export` (and visa-versa)\r\n* using an expected variable before `export` raises an error \r\n* code is run only once, at module load time, not later\r\n* methods do not run import code\r\n* all \"imports\" are at the top of the file\r\n\r\n\r\n### Solution #2: Use `delay_import`\r\n\r\nProvide a proxy which is responsible for import upon first use of the module variable.\r\n\r\n**foos.py**\r\n\r\n```python\r\nfrom mo_imports import delay_import\r\n\r\nbar = delay_import(\"bars.bar\")\r\n\r\ndef foo():\r\n bar()\r\n\r\n```\r\n\r\n**bars.py**\r\n\r\n```python\r\nfrom foos import foo\r\n\r\ndef bar():\r\n foo()\r\n```\r\n\r\n\r\n**Benefits**\r\n \r\n* cleaner code \r\n* costly imports are delayed until first use\r\n\r\n\r\n> WARNING\r\n> \r\n> Requires any of `__call__`, `__getitem__`, `__getattr__` to be called to trigger the\r\n> import. This means sentinals, placeholders, and default values can NOT be imported \r\n> using `delay_import()`\r\n\r\n\r\n## Other solutions\r\n\r\nIf you do not use `mo-imports` your import cycles can be broken using one of the following common patterns:\r\n\r\n\r\n### Bad Solution: Keep in single file\r\n\r\nYou can declare yet-another-module that holds the cycles\r\n\r\n**foosbars.py**\r\n\r\n```python\r\n def foo():\r\n bar()\r\n\r\n def bar():\r\n foo()\r\n```\r\n\r\nbut this breaks the code modularity\r\n\r\n\r\n### Bad Solution: Use end-of-file imports\r\n\r\nDuring 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. \r\n\r\n**foos.py**\r\n\r\n```python\r\ndef foo():\r\n bar()\r\n\r\nfrom bars import bar\r\n```\r\n\r\n**bars.py**\r\n\r\n```python\r\ndef bar():\r\n foo()\r\n\r\nfrom foos import foo\r\n```\r\n\r\nLinters do not like this pattern: You may miss imports, since these are hiding at the bottom.\r\n \r\n\r\n\r\n### Bad Solution: Inline import\r\n\r\nImport the name only when it is needed\r\n\r\n**foos.py**\r\n\r\n```python\r\ndef foo():\r\n from bars import bar\r\n bar()\r\n```\r\n \r\n**bars.py**\r\n\r\n\r\n```python\r\ndef bar():\r\n from foos import foo\r\n foo()\r\n```\r\n\r\nThis 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.\r\n \r\n\r\n\r\n### Bad Solution: Use the `_late_import()` pattern\r\n\r\nWhen other bad solutions do not work work, then importing late is the remaining option\r\n\r\n**foos.py**\r\n\r\n```python\r\nfrom bars import bar\r\n\r\ndef foo():\r\n bar()\r\n```\r\n\r\n**bars.py**\r\n\r\n```python\r\nfoo = None\r\n\r\ndef _late_import():\r\n global foo\r\n from foos import foo\r\n _ = foo\r\n\r\ndef bar():\r\n if not foo:\r\n _late_import()\r\n foo()\r\n```\r\n\r\nPlaceholders 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.\r\n \r\n\r\n## More on importing\r\n\r\nImporting 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 \r\n\r\n```python\r\nfrom my_lib.bars import bar\r\nfrom my_lib.foos import foo\r\n```\r\n\r\nanother module may choose the opposite order\r\n\r\n```python\r\nfrom my_lib.foos import foo\r\nfrom my_lib.bars import bar\r\n```\r\n\r\n### Ordering imports\r\n\r\nWith cyclic dependencies, ordering the imports can get tricky. Here are some rules \r\n\r\n* choose your principle modules and the order you want them imported.\r\n* your remaining modules are assumed to be imported in alphabetical order (as most linters prefer)\r\n* **use top level `__init__.py` to control the order of imports**\r\n* encourage third party modules to use this top level module. for example\r\n ```python\r\n from my_lib import foo, bar\r\n ```\r\n* finally, use `mo_imports` to break cycles\r\n",
"bugtrack_url": null,
"license": "MPL 2.0",
"summary": "More Imports! - Delayed importing",
"version": "7.671.25036",
"project_urls": {
"Homepage": "https://github.com/klahnakoski/mo-imports"
},
"split_keywords": [],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "f94d073446c511db012b550d160ff41b8f84195a0973176e0eef2d7cbd370311",
"md5": "7b75910e7801c1da841f935d5b073db4",
"sha256": "3f3322b9fbe731823168f52db14437ce9a4f641e3a04900185ae623d7d90850b"
},
"downloads": -1,
"filename": "mo_imports-7.671.25036-py3-none-any.whl",
"has_sig": false,
"md5_digest": "7b75910e7801c1da841f935d5b073db4",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": null,
"size": 11723,
"upload_time": "2025-02-05T01:20:14",
"upload_time_iso_8601": "2025-02-05T01:20:14.588761Z",
"url": "https://files.pythonhosted.org/packages/f9/4d/073446c511db012b550d160ff41b8f84195a0973176e0eef2d7cbd370311/mo_imports-7.671.25036-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "b8e2a37efda497dd57f03955ec7d210635bf02bf7e419223f64a9e375687fce5",
"md5": "0411b68e33e0ce7153799d1eb32b9768",
"sha256": "599f68f0402d2e298520f24ee0fff6511dc15259e742ac236f67a5ae8db2883b"
},
"downloads": -1,
"filename": "mo_imports-7.671.25036.tar.gz",
"has_sig": false,
"md5_digest": "0411b68e33e0ce7153799d1eb32b9768",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 12152,
"upload_time": "2025-02-05T01:20:16",
"upload_time_iso_8601": "2025-02-05T01:20:16.665749Z",
"url": "https://files.pythonhosted.org/packages/b8/e2/a37efda497dd57f03955ec7d210635bf02bf7e419223f64a9e375687fce5/mo_imports-7.671.25036.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-02-05 01:20:16",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "klahnakoski",
"github_project": "mo-imports",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "mo-imports"
}