From 7197a8d624160510662c58bf209c6d6ab18b952d Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Mon, 6 Mar 2023 08:49:00 -0700 Subject: [PATCH] Added documentation for import statements. --- README.md | 1 + docs/import-statements.md | 31 +++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 docs/import-statements.md diff --git a/README.md b/README.md index 44ba3de44..5d5ffc751 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,7 @@ To update to the latest version: * [Settings](/docs/settings.md) * [Comments](/docs/comments.md) * [Type Inference](/docs/type-inference.md) +* [Import Statements](/docs/import-statements.md) * [Differences from Mypy](/docs/mypy-comparison.md) * [Import Resolution](/docs/import-resolution.md) * [Extending Builtins](/docs/builtins.md) diff --git a/docs/import-statements.md b/docs/import-statements.md new file mode 100644 index 000000000..6a8a9b966 --- /dev/null +++ b/docs/import-statements.md @@ -0,0 +1,31 @@ +## Import Statements + +### Loader Side Effects + +An import statement instructs the Python import loader to perform several operations. For example, the statement `from a.b import Foo as Bar` causes the following steps to be performed at runtime: +1. Load and execute module `a` if it hasn’t previously been loaded. Cache a reference to a. +2. Load and execute submodule `b` if it hasn’t previously been loaded. +3. Store a reference to submodule `b` to the variable `b` within module `a`’s namespace. +4. Look up attribute `Foo` within module `b`. +5. Assign the value of attribute `Foo` to a local variable called `Bar`. + +If another source file were to subsequently execute the statement `import a`, it would observe `b` in the namespace of `a` as a side effect of step 3 in the the earlier import operation. Relying on such side effects leads to fragile code because a change in execution ordering or a modification to one module can break code in another module. Reliance on such side effects is therefore considered a bug by Pyright, which intentionally does not attempt to model such side effects. + +### Implicit Module Loads + +Pyright models two loader side effects that are considered safe and are commonly used in Python code. + +1. If an import statement targets a multi-part module name and does not use an alias, all modules within the multi-part module name are assumed to be loaded. For example, the statement `import a.b.c` is treated as though it is three back-to-back import statements: `import a`, `import a.b` and `import a.b.c`. This allows for subsequent use of all symbols in `a`, `a.b`, and `a.b.c`. If an alias is used (e.g. `import a.b.c as abc`), this is assumed to load only module `c`. A subsequent `import a` would not provide access to `a.b` or `a.b.c`. + +2. If an `__init__.py` file includes an import statement of the form `from .a import b`, the local variable `a` is assigned a reference to submodule `a`. This statement form is treated as though it is two back-to-back import statements: `from . import a` followed by `from .a import b`. + + +### Unsupported Loader Side Effects + +All other module loader side effects are intentionally _not_ modeled by Pyright and should not be relied upon in code. Examples include: + +- If one module contains the statement `import a.b` and a second module includes `import a`, the second module should not rely on the fact that `a.b` is now accessible as a side effect of the first module’s import. + +- If a module contains the statement `import a.b` in the global scope and a function that includes the statement `import a` or `import a.c`, the function should not assume that it can access `a.b`. This assumption might or might not be safe depending on execution order. + +- If a module contains the statements `import a.b as foo` and `import a`, code within that module should not assume that it can access `a.b`. Such an assumption might be safe depending on the relative order of the statements and the order in which they are executed, but it leads to fragile code.