Whoops, sorry. I just realized that I mistyped the third paragraph from the bottom, it should be:
> from some_library import AnotherClass # this causes * AnotherClass * to be imported from some_library/__init__.py which in turn causes the import statement on line 4 of some_library/__init__.py to finally happen for real. The import of *SomeClass* on line 3 *still* hasn't actually happened, and may never happen if *SomeClass* isn't imported (or lazy imported and then used) during the lifetime of this program. On Fri, Feb 5, 2021 at 12:34 PM Matt del Valle <matthew...@gmail.com> wrote: > I have seen a lot of interest in this topic, and I think the sheer number > of different lazy import implementations (generally using proxy objects for > modules and then loading them when an attribute is accessed) should present > pretty good evidence that this is a very desired feature, particularly for > writers of library code seeking to optimize start-up times and similar. > > Unfortunately, all of these implementations have serious limitations in > that often static code analysis tools cannot understand them, resulting in > autocompletions and useful diagnostic errors being lost. Also, they are > fundamentally incapable of dealing with 'from x import y' syntax, because > this is *always *loaded eagerly by the Python interpreter. > > With the increasingly widespread adoption of type hints I think it is time > to revive the proposal for lazy imports as an official language feature. > > It is very common to type hint the return type or argument type of a > function as a type which cannot be imported at the top of the file inside > the module namespace because it would trigger a circular import, and must > therefore be imported inside the function. In these instances it is > currently recommended to use the 'TYPE_CHECKING' value in the 'typing' > module which is always 'False' at runtime, in order to be able to provide > the return or argument type hint: > > some_module.py > > > from typing import TYPE_CHECKING > > > > if TYPE_CHECKING: > > from module_that_depends_on_me import MyClass > > > > > > def some_func() -> MyClass: > > from module_that_depends_on_me import MyClass > > > > return MyClass() > > > However, this is unnecessarily verbose and confusing to most users who are > not familiar with this usage pattern. IDEs will also be unable to warn you > that MyClass is undefined if you try to use it inside 'some_func' without > importing it within 'some_func' first, because it appears to be defined > even though it isn't. > > It also is fundamentally incapable of handling this other extremely common > usage pattern: > > some_library/ __init__.py > > > __all__ = ["SomeClass", "AnotherClass"] > > > > from .extremely_expensive_module import SomeClass > > from .another_extremely_expensive_module import AnotherClass > > some_module.py > > > from some_library import SomeClass > > > Where a library author wants to make several classes in their library's > public API available at package level in the __init__.py, but doesn't want > to eagerly load all of them when many users will only ever use one in a > given program. In this situation if I try to import SomeClass from > some_library/__init__.py I will *always* trigger AnotherClass to be > loaded as well, totally unnecessarily. > > My proposed syntax is pretty much the same as was suggested by Nicolas > Cellier in 2017 here: > https://mail.python.org/pipermail/python-ideas/2017-February/044894.html, > but with the additional stipulation that 'from x import y' format imports > should *also* be supported. The two new lazy import statements should > therefore be: > > > lazy import some_module > > from some_module lazy import SomeClass > > For the two examples given above the equivalent code should be. > > some_module.py > > > from module_that_depends_on_me lazy import MyClass # does not raise > ImportError when module_that_depends_on_me tries to import me, because this > import is deferred > > > > > > def some_func() -> MyClass: > > return MyClass() # MyClass is not imported until this line, in a > function that isn't called until after module level imports have already > been resolved > > some_library /__init__.py > > > __all__ = ["SomeClass", "AnotherClass"] > > > > from .extremely_expensive_module lazy import SomeClass # this import is > deferred > > from .another_extremely_expensive_module lazy import AnotherClass # this > import is deferred > > some_module.py > > > from some_library import AnotherClass # this causes SomeClass to be > imported from some_library/__init__.py which in turn causes the import > statement on line 4 of some_library/__init__.py to finally happen for real. > The import of AnotherClass on line 3 *still* hasn't actually happened, > and may never happen if AnotherClass isn't imported (or lazy imported and > then used) during the lifetime of this program. > > These are just some examples that I could think of off the top of my head. > I am certain there are more. > > This would be insanely useful. I really hope this sparks some discussion :) >
_______________________________________________ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-le...@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/4QY7ZZVZKUWPBOKW7OIVF3KJCFC3V3XX/ Code of Conduct: http://python.org/psf/codeofconduct/