GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — gh-142349: Simplify lazy import resolution by pablogsal · Pull Request #158282 · python/cpython

Conversation

@pablogsal

@pablogsal pablogsal commented Sep 27, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member

The current implementation has some problems. The eval loop and module attribute lookup repeat the code that resolves a lazy import and replaces its placeholder. These copies behave differently. We also hold the global import lock during resolution, which can deadlock with another thread importing a package.

This moves the placeholder structure and resolution code into lazyimportobject.c. One helper now resolves the import and updates the namespace where we found the name. It only replaces the value if it is still the same placeholder or the child module that the normal importer just put there. Assignments and deletions made during the import are preserved. For ordinary dicts, the check and replacement are atomic.

We also remove the global import lock around resolution. Importlib still locks modules while loading them. Each thread keeps a set of placeholders being resolved to detect cycles. Custom import hooks can now run concurrently.

This removes duplicate lookup and cleanup code. The eval loop and module attribute lookup use the same helper, so they no longer need separate implementations of these rules.

With this design, a placeholder records the module to import and where the import was declared. A name taken from that import holds a reference to the original placeholder and records the attribute to look up. When the name is first used, the resolver follows those references, imports the module and performs the attribute lookups in order. The shared helper then replaces the placeholder in the namespace where it was found, provided that binding still holds the placeholder or that child module.

  • Issue: Implement PEP 810 – Explicit lazy imports #142349

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
pablogsal added 7 commits September 27, 2026 15:19
@pablogsal
Move lazy resolution next to the placeholder
67ed535
Keep the placeholder structure private to lazyimportobject.c. Move the
resolution and attribute lookup code there so it can use the structure
without exposing its fields to the eval loop or import code.
@pablogsal
Track lazy resolution per thread
ac89dd0
Let importlib handle module locks instead of holding the global import
lock while resolving a placeholder. Keep active placeholders in a set on
the thread and remove them on every exit, including allocation failures.

Use the same cycle and recursion checks when a hook returns a placeholder.
Keep the source references alive until all attribute lookups finish.
@pablogsal
Keep the original lazy import error chain
846f934
Keep an existing cause or context when resolution fails. Add the import
location as a note in that case, without adding the same note twice.
Errors without an existing chain still get the declaration as their cause.
@pablogsal
Avoid waiting in the lazy from shortcut
dd01552
Only reuse a concrete attribute from a module that has finished loading.
Leave lazy attributes for resolution so module hooks still get a chance.
Check the module again after reading its spec, and keep interrupts visible.
@pablogsal
Simplify lazy import declarations
b78ebc9
Normalize the fromlist before calling the filter and check its items
before registering imports. Use one cleanup path and the existing filter
accessor. Pass the same five arguments to custom lazy hooks as to __import__.
@pablogsal
Handle lazy module hooks and children together
917df44
Resolve lazy __getattr__ and __dir__ hooks before calling them. Treat a
hook already being resolved as unavailable so it can import a sibling.

Bind a loaded child before removing its pending entry. Recheck the module
dict when another thread may have completed the load. Reuse modules already
in sys.modules during package cycles, and simplify child registration.
@pablogsal
Share lazy import rebinding
4dc64c8
Remember the namespace where lookup found the placeholder and use one
helper to resolve and replace it. Only replace a binding that still holds
the same placeholder, preserving assignments and deletions during import.

Check and replace ordinary dict entries atomically. Keep the mapping
protocol for custom namespaces and allow reads from readonly namespaces.
Reuse the global lookup helper in the eval loop and remove the duplicate.
@pablogsal
pablogsal requested review from DinoV, FFY00, Yhg1s, ZeroIntensity, brettcannon, ericsnowcurrently, kumaraditya303, markshannon, methane, ncoghlan and warsaw as code owners September 27, 2026 14:21
@bedevere-app bedevere-app Bot mentioned this pull request Sep 27, 2026
Implement PEP 810 – Explicit lazy imports #142349
Closed
@bedevere-app bedevere-app Bot added the awaiting core review label Sep 27, 2026
@pablogsal

pablogsal commented Sep 27, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member Author

@brittanyrey this cleans up a bit of the lazy import code by sharing the resolve and replace logic and keeping the placeholder details in one file. It also removes the global import lock around resolution. Could you take a look?

brittanyrey reacted with heart emoji
All reactions
  • ❤️ 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@pablogsal pablogsal added the skip news label Sep 27, 2026
@pablogsal
Keep imported attributes on their parent package
ad34fae
Loading a child can replace the parent binding with the module before we fetch the imported attribute. Keep the module returned by the normal importer so the shared helper can replace it in its actual parent namespace. Other values and deletions still prevent replacement, and custom hooks keep control of their assignments.
@pablogsal pablogsal mentioned this pull request Sep 29, 2026
gh-155695: Remove resolved names from sys.lazy_modules more consistently #157714
Merged
@encukou

encukou commented Sep 30, 2026

Copy link
Copy Markdown
Member

Marking a release blocker for @hugovk's consideration, per #157714 (comment) . I'll start the review.

But, I must say that I feel the process is broken somewhere. From my side it, subjectively, looks like the issues that led here were ignored (1, 2) and argued as unnecessary by the PEP authors for months, so +884/-946 PR this late in the RC period is quite a surprise. I'm not sure what to do so we don't get this situation again. But, that's a discussion for after the release.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@hugovk hugovk added the release-blocker label Sep 30, 2026
@github-project-automation github-project-automation Bot added this to Release and Deferred blockers 🚫 Sep 30, 2026
@github-project-automation github-project-automation Bot moved this to Todo in Release and Deferred blockers 🚫 Sep 30, 2026
@hugovk hugovk added the 🔨 test-with-buildbots Test PR w/ buildbots; report in status section label Sep 30, 2026
@bedevere-bot

bedevere-bot commented Sep 30, 2026

Copy link
Copy Markdown

🤖 New build scheduled with the buildbot fleet by @hugovk for commit ad34fae 🤖

Results will be shown at:

https://buildbot.python.org/all/#/grid?branch=refs%2Fpull%2F158282%2Fmerge

If you want to schedule another build, you need to add the 🔨 test-with-buildbots label again.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@encukou encukou mentioned this pull request Oct 1, 2026
Tests & nitpicks for the lazy import refactor pablogsal/cpython#145
Merged
encukou
encukou approved these changes Oct 1, 2026
View reviewed changes

@encukou encukou left a comment

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand the nuances of lazy imports yet, but the refactor looks sound.

I noticed some untested parts, and collected nitpicks/suggestions as I went along. I sent these in pablogsal#145; feel free to merge or cherry-pick what you agree with.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@bedevere-app bedevere-app Bot added awaiting merge and removed awaiting core review labels Oct 1, 2026
@hugovk hugovk added the needs backport to 3.15 bugs and security fixes label Oct 1, 2026
@hugovk
hugovk enabled auto-merge (squash) October 1, 2026 14:36
@hugovk
hugovk disabled auto-merge October 1, 2026 14:36
encukou and others added 2 commits October 1, 2026 17:16
@encukou @hugovk
Apply batched suggestions from code review
e44cc18
Co-authored-by: Hugo van Kemenade <[email protected]>
@pablogsal
Merge pull request #145 from encukou/simplify-lazy-imports-c
b09a509
Tests & nitpicks for the lazy import refactor
@hugovk
hugovk enabled auto-merge (squash) October 1, 2026 15:46
@pablogsal pablogsal mentioned this pull request Oct 1, 2026
[3.15] gh-142349: Simplify lazy import resolution (GH-158282) #158569
Merged
@bedevere-app

bedevere-app Bot commented Oct 1, 2026

Copy link
Copy Markdown

GH-158569 is a backport of this pull request to the 3.15 branch.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@bedevere-app bedevere-app Bot removed the needs backport to 3.15 bugs and security fixes label Oct 1, 2026
@hugovk
hugovk merged commit a6a63b6 into python:main Oct 1, 2026
75 of 77 checks passed

Uh oh!

There was an error while loading. Please reload this page.

@bedevere-app bedevere-app Bot removed the awaiting merge label Oct 1, 2026
@pablogsal pablogsal added the needs backport to 3.15 bugs and security fixes label Oct 1, 2026
@miss-islington-app

miss-islington-app Bot commented Oct 1, 2026

Copy link
Copy Markdown

Thanks @pablogsal for the PR, and @hugovk for merging it 🌮🎉.. I'm working now to backport this PR to: 3.15.
🐍🍒⛏🤖

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@miss-islington-app

miss-islington-app Bot commented Oct 1, 2026

Copy link
Copy Markdown

Sorry, @pablogsal and @hugovk, I could not cleanly backport this to 3.15 due to a conflict.

Please backport manually with cherry_picker, see the devguide for more information.

cherry_picker a6a63b6f5b5e4b921dd98ca8eeea90bca3519814 3.15
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@miss-islington-app miss-islington-app Bot assigned hugovk Oct 1, 2026
pablogsal added a commit that referenced this pull request Oct 1, 2026
@pablogsal @encukou @hugovk
[3.15] gh-142349: Simplify lazy import resolution (GH-158282) (#158569)
021f634
Backport the shared lazy import resolver to Python 3.15, regenerate
interpreter files, and update the ABI snapshot.

Co-authored-by: Petr Viktorin <[email protected]>
Co-authored-by: Hugo van Kemenade <[email protected]>
@pablogsal pablogsal removed the needs backport to 3.15 bugs and security fixes label Oct 1, 2026
Kronuz added a commit to Kronuz/cpython that referenced this pull request Oct 1, 2026
@Kronuz
Parity check to 3.15 tip 021f634 (release day): lazy core refactored
b60a5ad
pythonGH-158282 landed a shared lazy-import resolver on 3.15 release day,
rewriting lazyimportobject.c/import.c, changing the PyLazyImportObject
layout and opcode metadata. The 3.14 backport predates it and needs a
deliberate re-sync (not a cherry-pick), which subsumes the earlier
pythongh-157757 item. gc-stats surface unchanged. Advance tracked hash to
021f634.
Kronuz added a commit to Kronuz/cpython that referenced this pull request Oct 1, 2026
@Kronuz
Backport pythonGH-158282 lazy-import behaviors and refresh tests to t…
e26052a
…he 3.15 tip

Ports two user-visible behaviors from upstream's pythonGH-158282 lazy-import
resolver refactor without its 3.15-only dict/eval infrastructure:

- Chained exception notes: when a lazy import's resolution fails and the
  exception already carries a cause/context (including `raise ... from
  None`), attach a "lazy import of '<name>' declared in ..." note instead
  of replacing the cause (import.c error path).
- __lazy_import__ fromlist validation: reject a non-str item in the from
  list with TypeError, matching eager __import__, instead of letting it
  reach code that assumes str and crash a debug build.

Also refreshes Lib/test/test_lazy_import to the 3.15 tip (021f634) so the
latest upstream lazy-import tests run here. Full suite passes (133 tests).
Kronuz added a commit to Kronuz/cpython that referenced this pull request Oct 1, 2026
@Kronuz
Record the pythongh-157757 + pythonGH-158282-behaviors backport (clea…
418fded
…npython314 PR python#47)
@brittanyrey

brittanyrey commented Oct 1, 2026

Copy link
Copy Markdown
Contributor

Will continue tomorrow, or sooner if I find the time. I can merge #157714 with this -- @brittanyrey, let me know if you've started on that or want to do it yourself.

@encukou That would be much appreciated! I'm out on PTO for the next few days.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@encukou encukou mentioned this pull request Oct 1, 2026
Lazy import triggers C assert if reading spec._initializing raises #158402
Closed
Kronuz added a commit to Kronuz/cpython that referenced this pull request Oct 5, 2026
@Kronuz
Backport pythonGH-158282 lazy-import behaviors and refresh tests to t…
287a039
…he 3.15 tip

Ports two user-visible behaviors from upstream's pythonGH-158282 lazy-import
resolver refactor without its 3.15-only dict/eval infrastructure:

- Chained exception notes: when a lazy import's resolution fails and the
  exception already carries a cause/context (including `raise ... from
  None`), attach a "lazy import of '<name>' declared in ..." note instead
  of replacing the cause (import.c error path).
- __lazy_import__ fromlist validation: reject a non-str item in the from
  list with TypeError, matching eager __import__, instead of letting it
  reach code that assumes str and crash a debug build.

Also refreshes Lib/test/test_lazy_import to the 3.15 tip (021f634) so the
latest upstream lazy-import tests run here. Full suite passes (133 tests).
Kronuz added a commit to Kronuz/cpython that referenced this pull request Oct 5, 2026
@Kronuz
[3.15] pythongh-142349: Simplify lazy import resolution (pythonGH-158282
cc8f28a
) (python#158569)
Kronuz added a commit to Kronuz/cpython that referenced this pull request Oct 5, 2026
@Kronuz
Update the PEP 810 backport entry for the faithful 3.14.8 resolver
13e6f6e
3.14-lazy-imports now rebases onto v3.14.8 and carries the 3.15 resolver
(pythonGH-158282) and its follow-ups, not the earlier behavioral port. Refresh
the base version, the What list (sys.lazy_modules, ForwardRef.evaluate),
the provenance (the resolver commit and follow-ups, tracking the 3.15
tip), and the ABI note (the per-thread _PyThreadStateImpl.lazy_imports
field and the _PyDict_LoadGlobal compatibility shim).
meta-codesync Bot pushed a commit to facebookincubator/cinderx that referenced this pull request Oct 6, 2026
@itamaro @meta-codesync
Reify lazy imports in JIT loadGlobal like LOAD_GLOBAL (3.15rc3+)
36cb009
Summary: Since python/cpython#158282 ("Simplify lazy import resolution", in 3.15.0rc3 and main), `LOAD_GLOBAL` resolves a lazy import and swaps the result into the dict it was found in (globals or builtins), but only if the placeholder is still bound there. It also cleans up `sys.lazy_modules`. JIT'd `loadGlobal` resolved the placeholder but never rebound it, so the placeholder stayed in the module dict and every later JIT load resolved it again. On 3.15rc3+ `loadGlobal` now calls `_PyEval_LoadGlobalStackRef`, the same helper as `LOAD_GLOBAL`, which also covers the NameError and non-dict cases.

Reviewed By: yoney

Differential Revision: D123304416

fbshipit-source-id: 3ce34c8aff6b09bc32c19493de51263e28fde6be
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@encukou encukou encukou approved these changes
@ericsnowcurrently ericsnowcurrently Awaiting requested review from ericsnowcurrently ericsnowcurrently is a code owner
@ZeroIntensity ZeroIntensity Awaiting requested review from ZeroIntensity ZeroIntensity is a code owner
@FFY00 FFY00 Awaiting requested review from FFY00 FFY00 is a code owner
@Yhg1s Yhg1s Awaiting requested review from Yhg1s Yhg1s is a code owner
@DinoV DinoV Awaiting requested review from DinoV DinoV is a code owner
@brettcannon brettcannon Awaiting requested review from brettcannon brettcannon is a code owner
@ncoghlan ncoghlan Awaiting requested review from ncoghlan ncoghlan is a code owner
@warsaw warsaw Awaiting requested review from warsaw warsaw is a code owner
@kumaraditya303 kumaraditya303 Awaiting requested review from kumaraditya303 kumaraditya303 is a code owner
@markshannon markshannon Awaiting requested review from markshannon markshannon is a code owner
@methane methane Awaiting requested review from methane methane is a code owner

Uh oh!

There was an error while loading. Please reload this page.

Assignees

@hugovk hugovk

Labels

release-blocker skip news type-feature A feature request or enhancement

Projects

Release and Deferred blockers 🚫
Status: Done

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Uh oh!

There was an error while loading. Please reload this page.

5 participants

@pablogsal @encukou @bedevere-bot @brittanyrey @hugovk
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.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.