Repository navigation
Conversation
Six imports sat inside the functions that used them, each with a comment explaining why, and none where a reader looks for a module's dependencies. PEP 810 lets the language express that instead: declared in __lazy_modules__, they stay deferred while reading as ordinary imports. Python 3.15 honors the declaration natively. Earlier versions reach the same behavior through the lazy_importer backport, kept behind compat/py314 so the shared module carries only one import and one with statement, both removed when syncing to the stdlib. Costs about 1-2 ms of import time before 3.15, the price of importing lazy_importer, and nothing measurable on 3.15. Closes #554 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #554.
Puts all six function-local imports back at module scope and keeps them deferred by declaring
__lazy_modules__(PEP 810).Shape
__lazy_modules__is only a module global, so it is inert before 3.15 and needs no guard — the declaration reads the same in both trees, and__name__keeps the entries for this package's own modules identical too. The one entry that has to differ iszipfile, next to the import line that already differs for thezippoverlay; there is a comment on it so the sync step is visible.Same behavior before 3.15 needs the backport, and it lives behind
compat/py314in the shapecompatalready uses, so the shared module carries onlyfrom .compat.py314 import lazy_importerandwith lazy_importer():. Both come out at sync time likefrom .compat import py311already does. The one real sync cost is that the imports inside the block are indented and have to be dedented._compat/localize.pygets the same treatment, not for its own sake but because it is imported eagerly — leaving_adaptersat module scope there would have undone the deferral in__init__.py.Two things worth knowing
from . import _adaptersdoesn't work. Per PEP 810 that form names the package as the module to defer, and the package is mid-import, so it defers nothing; on the backport it recurses until the stack is exhausted. Importing the member instead keys the declaration on_adaptersand works on both, at the cost of_adapters.MessagebecomingMessageat two call sites. Both declarations carry a note, since the next person to add an entry will hit this.inspectcan't be observed as deferred on 3.12 and 3.13, whereimportlib.abc— eagerly imported here — reaches it throughimportlib.resources._common. The declaration is still right; the module is just loaded for other reasons.test_declared_modules_stay_unloadedimportsimportlib.abcbefore taking its snapshot so that it measures the declaration and not the stdlib's own graph.Cost
Per the project's own
pytest-perfexercise:Ranges because repeated runs on one machine disagree by that much — the honest figure is 1 to 2 ms before 3.15, and nothing measurable on 3.15. The entire cost is importing
lazy_importeritself (its module body, pluscontextlib,threadingandimportlib.util); the deferrals it buys back are the ones already in place.lazy-importerbecomes a dependency below 3.15. It is pure Python with no dependencies of its own, but it is at 0.1.0, which is worth weighing for a package this far down the stack — say the word and I'll makecompat/py314fall back to eager imports when it is absent, at the price of uniformity.Verification
tox -e py,py311,py312,py313,py314— 266/267 passed on each, mypy clean, coverage 99%;tox -e docsandtox -e diffcovclean, diff coverage 100%. Beyond the test above,test_declared_modules_resolvechecks that reading a lazy name yields the real module and not one of the backport's proxies, which is whatisinstanceandtype()comparisons would otherwise trip over.🤖 Generated with Claude Code