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-157614: Fix `lazy import a.b as c` incorrectly binding `a.b.b` by grgalex · Pull Request #157626 · python/cpython

Conversation

@grgalex

@grgalex grgalex commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

lazy import a.b as c binds c to a.b.b instead of the module a.b when a.b is already in sys.modules and has an attribute named b.

Root cause

import a.b as c compiles to IMPORT_NAME a.b followed by IMPORT_FROM b. For a lazy import, IMPORT_NAME returns a placeholder with lz_from = "a.b".

_PyEval_LazyImportFrom() has a fast path for modules already in sys.modules. It looks up lz_from directly and gets name from that module. In this case that means looking up b on a.b, producing a.b.b, instead of looking up b on the base module a.

Fix

Compute the base module before the sys.modules fast path and perform the lookup against it.

The fast path must also be limited to the last IMPORT_FROM identifiable from the placeholder. Longer dotted names compile to multiple IMPORT_FROM instructions, and resolving an intermediate step would make the rest of the import eager. Eight existing test_lazy_import tests cover this.

Two-component imports and from imports remain eligible for the fast path.

Test

Adds MixedLazyEagerImportTests.test_eager_dotted_import_before_lazy_resolves_to_same_module() alongside the existing single-name test.

It creates a package a with a/b.py defining an attribute b, then checks:

import a.b as c
lazy import a.b as lazy_c

assert lazy_c is c

The test fails on main because lazy_c resolves to a.b.b and passes with the fix. test_lazy_import, test_import, and test_importlib also pass.

Commits

  1. Move the lazy import subprocess helpers to LazyImportTestCase so the regression test can reuse them. No behavior change.
  2. Fix _PyEval_LazyImportFrom(), with the regression test and NEWS entry.

Future Work / Not Addressed

If package a itself has an attribute b shadowing submodule a.b, the lazy import can still reify to that attribute rather than the submodule. This is a separate mechanism, already tracked in gh-151208. The same mechanism makes lazy import math.pi as x bind the float where the eager statement raises; gh-155194 fixed the bare lazy import math.pi form only.

For names with three or more components, an already-loaded module still resolves on first use rather than at the import statement because later IMPORT_FROM steps cannot tell whether they are the last one. Fixing that would require additional compiler or placeholder information and is only an optimization.

Closes gh-157614.

Sorry, something went wrong.

Uh oh!

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

All reactions
grgalex added 2 commits September 16, 2026 16:52
@grgalex
pythongh-157614: Make the lazy import subprocess test helper reusable
cfba96e
Move _run_subprocess_with_modules() and _assert_subprocess_ok() from
FilterFunctionSignatureTests to LazyImportTestCase, so that any lazy import
test can build a package tree in a temporary directory and run code
against it in a subprocess.

Signed-off-by: Georgios Alexopoulos <[email protected]>
@grgalex
pythongh-157614: Fix lazy import a.b as c binding a.b.b instead of th…
3398871
…e module

_PyEval_LazyImportFrom() ran its sys.modules fast path on lz_from, which
for lazy import a.b as c is the whole dotted name a.b rather than the
module a that b has to be taken from.

Changes to _PyEval_LazyImportFrom():

1. Compute the base module before the fast path instead of after it, and
   look the attribute up on the base.  The base is lz_from up to the
   first dot when lz_attr is NULL, lz_from + "." + lz_attr when lz_attr
   is a str, and lz_from itself for a from-import.

2. Add last_step and skip the fast path when it is false.  It is true
   for a from-import and for a placeholder without lz_attr whose name has
   a single dot, and false when lz_attr is a str, that is on the later
   steps of a name with three or more components.  Resolving one of those
   eagerly would hand a real module to the next IMPORT_FROM, which then
   imports the rest of the name right away instead of deferring it.

3. Hold the base in one owned reference, from, released on every exit,
   so the three _PyLazyImport_New() call sites collapse into one.

Add a test that imports a.b, where a/b.py defines an attribute b, and
checks that lazy import a.b as x binds the module.

Signed-off-by: Georgios Alexopoulos <[email protected]>
@grgalex
grgalex requested review from DinoV, Yhg1s, markshannon and pablogsal as code owners September 16, 2026 14:31
@python-cla-bot

python-cla-bot Bot commented Sep 16, 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 Sep 16, 2026
lazy import a.b as c incorrectly binds to a.b.b #157614
Open
@bedevere-app bedevere-app Bot added the awaiting review label Sep 16, 2026
@grgalex grgalex changed the title gh-157614: Fix lazy import a.b as c incorrectly binding a.b.b instead of the module gh-157614: Fix lazy import a.b as c incorrectly binding a.b.b Sep 16, 2026
@grgalex grgalex mentioned this pull request Sep 18, 2026
lazy import a.b as c resolves b as an attribute of a instead of importing the module a.b #157757
Closed
@Kludex Kludex mentioned this pull request Sep 23, 2026
Track plugin-discovery crash with Python 3.15 global lazy imports pydantic/pydantic#13854
Closed
1 task done
@hugovk hugovk added the release-blocker label Sep 24, 2026
@github-project-automation github-project-automation Bot added this to Release and Deferred blockers 🚫 Sep 24, 2026
@github-project-automation github-project-automation Bot moved this to Todo in Release and Deferred blockers 🚫 Sep 24, 2026
@hugovk hugovk added the needs backport to 3.15 bugs and security fixes label Sep 26, 2026
@hugovk

hugovk commented Sep 26, 2026

Copy link
Copy Markdown
Member

@grgalex Please could you resolve the merge conflict?

@brittanyrey @pablogsal Please could you review this PR?

All reactions

Sorry, something went wrong.

Uh oh!

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

@grgalex

grgalex commented Sep 26, 2026 •
edited
Loading

Uh oh!

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

Copy link
Copy Markdown
Contributor Author

@hugovk I was tracking the progress (and merged PR) on the adjacent issue.

I tested the very elegant merged solution by @brittanyrey and @pablogsal, and it appears to coincidentally also have fixed #157614, which the present PR tries to address.

As such, I understand that the main code changes by this PR are no longer necessary.

If it also makes sense to you, we can keep the regression test, as it is an interesting case.

All reactions

Sorry, something went wrong.

Uh oh!

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

@hugovk

hugovk commented Sep 28, 2026

Copy link
Copy Markdown
Member

Great, confirmed fixed :)

I'll remove the release-blocker label and let @pablogsal to decide if we need the extra test.

All reactions

Sorry, something went wrong.

Uh oh!

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

@hugovk hugovk removed the release-blocker label Sep 28, 2026
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
@markshannon markshannon Awaiting requested review from markshannon markshannon is a code owner

Uh oh!

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

Assignees

No one assigned

Labels

awaiting review needs backport to 3.15 bugs and security fixes

Projects

Release and Deferred blockers 🚫
Status: Todo

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

lazy import a.b as c incorrectly binds to a.b.b

Uh oh!

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

2 participants

@grgalex @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.