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-158597: Resolve lazy submodules with the declaration in scope by xv1rcn · Pull Request #159476 · python/cpython

Conversation

@xv1rcn

@xv1rcn xv1rcn commented Oct 15, 2026

Copy link
Copy Markdown

gh-158597: Resolve lazy submodules with the declaration in scope

lazy import xml.missing records a pending child in interp->imports.lazy_pending_submodules, keyed only by (parent, child):

pending["xml"]["missing"] =

register_lazy_on_parent() writes that slot with PyDict_SetItem(), so a later declaration of the same name by another module replaces the earlier one. When a module then evaluates xml.missing, _PyImport_TryLoadLazySubmodule() reads that one slot and _PyLazyImport_LoadChild() copies lz_code / lz_instr_offset out of it, so the chained ImportError reports whichever declaration was registered last, no matter who is accessing.

Measured before the change (main at 64018ac, debug build):

declarations, in order reported location
A only A.py:1
B, then A A.py:2
A, then B B.py:1
A, B, C C.py:1

This keys the declarations by the declaring module and selects one at access time from the frame's globals, so a name resolves with the declaration in scope where the access is written:

pending["xml"]["missing"] == {"A": , "B": } # declarations pending["xml"]["dom"] == None # pending, no declaration

A declaration made by another module still names the same child and imports the same thing, so it is still used when there is nothing in scope. It is not reported as a frame of the accessing module's traceback: lz_attributed on the placeholder records whether the location belongs to the module performing the access, and lazy_import_add_exception_cause() only adds a frame when it does. The chained ImportError then carries no frame rather than pointing into an unrelated file, and the exception type is unchanged.

One visible change beyond the reported case: _PyLazyImport_LoadChild() takes lz_builtins from the declaration it is given, so the selected declaration also decides which __import__ runs. An access with a declaration of its own in scope now runs that module's hook instead of the last-registered one's — the case Yhg1s described in the gh-158521 review ("module A may end up calling module B's __import__ when it does its reification"), and the principle test_namespace_import_hooks already covers for placeholders. It does not cover it completely: an access with no declaration in scope still resolves with the most recently registered one, as before, so that case is unchanged.

Remaining gaps:

  • Two modules sharing the same __name__ are still indistinguishable, as noted in the gh-158140: Resolve lazily imported sibling submodules independently #158521 review discussion.
  • add_lazy_declaration() reads and then writes the per-child dict. The surrounding code already reads lazy_pending_submodules without a lock in _PyImport_TryLoadLazySubmodule(), and the consequence here is only that concurrent registrations of the same child can drop one declaration (the child stays pending and is resolved by the remaining one). I did not add locking because it would be a separate change; happy to do it if you want it in this PR.

Testing

  • ./python -m test test_lazy_import test_import test_importlib test_module test_sys test_capi — 3267 tests pass (debug build); -R 3:3 clean.
  • The same on a --disable-gil --with-pydebug build.
  • Two regression tests in ErrorHandlingTests, both failing on unmodified main:
  • test_pending_child_reports_accessing_module_declaration
  • test_unattributed_access_is_not_given_another_declaration
  • NEWS entry added.

AI assistance was used to locate the cause and to draft the change and the tests. I have reviewed the analysis and the patch and can explain every part of the diff.

Sorry, something went wrong.

Uh oh!

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

All reactions
@xv1rcn
xv1rcn requested review from DinoV, FFY00, Yhg1s, brettcannon, ericsnowcurrently, kumaraditya303, ncoghlan, pablogsal and warsaw as code owners October 15, 2026 16:08
@python-cla-bot

python-cla-bot Bot commented Oct 15, 2026 •
edited
Loading

Uh oh!

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

Copy link
Copy Markdown

All commit authors signed the Contributor License Agreement.

CLA signed

All reactions

Sorry, something went wrong.

Uh oh!

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

@bedevere-app bedevere-app Bot mentioned this pull request Oct 15, 2026
Lazy import traceback points to another module's declaration #158597
Open
@bedevere-app bedevere-app Bot added the awaiting review label Oct 15, 2026
@xv1rcn
pythongh-158597: Resolve lazy submodules with the declaration in scope
00db886
A pending child in `lazy_pending_submodules` is keyed only by
`(parent, child)`, so a later declaration of the same name by another module
replaces the earlier one. Resolving the child then copies the location out of
the surviving declaration, so the chained `ImportError` points into the file
of a module that merely declared the name.

Key the declarations by the declaring module, and select one from the frame's
globals at access time, so the child resolves with the declaration in scope
where the access is written. A declaration made by another module still names
the same child and imports the same thing, so it is still used when there is
nothing in scope; its location is then not reported as a frame of the
accessing module's traceback.

The selection also decides which `__import__` runs, since
`_PyLazyImport_LoadChild()` takes `lz_builtins` from the declaration it is
given. An access that has a declaration of its own in scope now uses that
module's hook instead of the last-registered one's — the case Yhg1s described
in the pythongh-158521 review. An access with no declaration in scope is unchanged.
@xv1rcn
xv1rcn force-pushed the gh-158597-lazy-declaration-scope branch from 3762e81 to 00db886 Compare October 15, 2026 16:11
@pablogsal pablogsal closed this Oct 15, 2026
@pablogsal

pablogsal commented Oct 15, 2026 •
edited
Loading

Uh oh!

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

Copy link
Copy Markdown
Member

I'm closing this per our AI policy. Please don't open more PRs without achieving first consensus in the issue on the approach

All reactions

Sorry, something went wrong.

Uh oh!

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@Yhg1s Yhg1s Awaiting requested review from Yhg1s Yhg1s is a code owner
@DinoV DinoV Awaiting requested review from DinoV DinoV is a code owner
@pablogsal pablogsal Awaiting requested review from pablogsal pablogsal is a code owner
@brettcannon brettcannon Awaiting requested review from brettcannon brettcannon is a code owner
@ericsnowcurrently ericsnowcurrently Awaiting requested review from ericsnowcurrently ericsnowcurrently 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
@FFY00 FFY00 Awaiting requested review from FFY00 FFY00 is a code owner
@kumaraditya303 kumaraditya303 Awaiting requested review from kumaraditya303 kumaraditya303 is a code owner

Uh oh!

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

Assignees

No one assigned

Labels

awaiting review

Projects

None yet

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.

2 participants

@xv1rcn @pablogsal
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.