Imports
Imports follow Python’s syntax, with one Typhon addition (lazy import) and one target-dependent form (lazy from, which needs a 3.15 target).
import
import sysimport os.pathimport jsonimport requests as rqStandard Python — names land in the importing module’s namespace.
from ... import
from typing import Callablefrom collections.abc import Iterablefrom .users import Userfrom ..lib import helperRelative imports (., ..) work as in Python. Since v0.9.0 the in-process VM (tyc run) also resolves relative imports against the project source root — sibling .ty modules load on demand and each module’s bindings are cached as a Value::Module. Multi-file projects no longer require --compile to handle the cross-module imports.
tyc run --compile was tweaked at the same time: instead of python build/main.py, it spawns python -m <pkg>.main so relative imports in the entry point resolve correctly under the compiled path too.
typing names
Inside annotations and type statements — which the emitted module never
evaluates — Union, Optional, Any, List / Dict / Set / Tuple /
FrozenSet, Type, TypeVar, Generic, Self, ClassVar, Final,
Literal and NoReturn can be written without an import. Used as runtime
values they need one, because CPython would raise NameError:
def describe(hint: object) -> str: return "union" if hint is Union else "other" # ❌ tyc::unknown_namefrom typing import Union
def describe(hint: object) -> str: return "union" if hint is Union else "other" # ✅Protocol and the collections.abc names (Callable, Iterator, …) are
unaffected: the build imports them. The deprecated capitalised aliases and
TypeVar cannot be imported at all (tyc::typing_alias_deprecated,
tyc::typevar_import_rejected); use the lowercase built-ins and PEP 695
type parameters.
Conditional imports
import sys
if sys.platform == "win32": import winregelse: import osStandard Python — Typhon doesn’t restrict this.
Re-exports
A module-level __all__ list controls what from module import * exposes:
# in users.ty__all__ = ["User", "find_user"]For imports the resolver follows __all__ when computing what a from module import provides.
lazy import
Defers module loading until first attribute access:
lazy import np = numpylazy import torchThe first form gives the proxy an alias; the second uses the module name directly. See lazy for the full reference.
lazy from needs Python 3.15
lazy from numpy import array # ✅ on a 3.15 target, ❌ on 3.13 / 3.14error[tyc::requires_newer_python]: `lazy from … import …` needs Python 3.15 (PEP 810) and the project targets 3.13; use `lazy import ALIAS = MODULE` insteadBefore PEP 810 a from-import has to load the module to bind the name, so it cannot defer. On a 3.15 target the native statement makes the name itself lazy, and tyc build emits it as written.
Unused imports
Severity controlled by [strictness] unused-import in typhon.toml:
"warn"— default since v0.8.0 (was"error"before). Unused imports surface as warnings."error"— fail the build on unused imports. Set this for CI strictness or to enforce a clean import surface."off"— no check.
The LSP offers a “Remove unused import” quick-fix.
Import side effects
Python imports run module-level code. Typhon does not change this — import some_module runs some_module/__init__.py as it always has.
Use lazy import to defer that cost when it matters (e.g. for CLIs that may not need the module).
Cyclic imports
Typhon does not introduce new rules. Python’s existing semantics apply: cyclic imports work as long as the cycle doesn’t access module-level names before they’re defined. The checker doesn’t enforce a “no cycles” rule.
Where the checker treats .pyi / .py as boundaries
When you import from a plain .py (no .dty stub authored), the imported symbols are treated as if they crossed an unsafe: boundary unless they’re typed in the source. Use .dty stubs to give third-party libraries proper typed signatures.
Where next
lazy— deferred import reference.- Writing .dty Stubs — typing third-party imports.
- Interop Overview — Python interop story.