python / python/cpython

`lazy import a.b as c` resolves `b` as an attribute of `a` instead of importing the module `a.b`

Đang mở
#157,757 1 bình luận 0 reaction 0 người được giao Xem trên GitHub

Chưa có ai nhận issue này.

interpreter-core release-blocker topic-lazy-imports type-bug
Ngôn ngữ chính
Python
Star
77.2k
Fork
35.9k
Chỉ số merge pull request
Chỉ số pull request đang chờ

Mô tả

Bug report

Bug description:

lazy import a.b as c never imports the module a.b by name. On first use it imports a and reads the attribute b off it, as lazy from a import b would, so c becomes whatever that attribute lookup finds. This manifests in two ways.

Case A: a.b is not a module. The eager statement raises; the lazy one binds the attribute.

lazy import math.pi as lazy_x
print(lazy_x)  # 3.141592653589793

import math.pi as x  # ModuleNotFoundError: No module named 'math.pi'; 'math' is not a package

The bare lazy import math.pi was fixed in #155194; the as form was not.

Case B: a.b is a module, but a has an attribute b that shadows it and a.b has not been imported yet. The eager statement imports the submodule, which rebinds a.b to the module; the lazy one binds the attribute and never imports the submodule. json does not import json.tool, so the assignment below stands in for an attribute a package's __init__.py would define:

import json
json.tool = "attribute"

lazy import json.tool as lazy_c
print(type(lazy_c).__name__)  # str

import json.tool as c
print(type(c).__name__)  # module

Case B is related to #151208, which reports the same shadowing for import a.foo under -X lazy_imports=all followed by an attribute access a.foo, resolved in Objects/moduleobject.c. That script now passes on main; this statement still fails, because it is resolved on a different path.

Possible root cause

import a.b as c compiles to IMPORT_NAME a.b followed by IMPORT_FROM b. Lazily, IMPORT_NAME leaves a placeholder holding a.b, and _PyEval_LazyImportFrom (Python/ceval.c:3329-3384) replaces it with one holding lz_from = "a", lz_attr = "b". That is the same placeholder lazy from a import b produces.

Reification in _PyImport_LoadLazyImportTstate (Python/import.c:3902-4093) therefore runs __import__("a", fromlist=("b",)) followed by getattr(a, "b") for both statements. Nothing records that the statement was an import, so the module a.b is never imported by its own name: the "is not a package" check never runs, and an existing attribute b is never overwritten by the submodule.

This is not #157614, which is about the sys.modules fast path in _PyEval_LazyImportFrom consulting the wrong module. The present issue reproduces with the fix in #157626 applied.

CPython versions tested on:

3.15

Operating systems tested on:

Linux

Hướng dẫn đóng góp

Mở hướng dẫn đóng góp

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Hướng nghiên cứu

Bắt đầu bằng cách đọc _PyEval_LazyImportFrom trong Python/ceval.c và _PyImport_LoadLazyImportTstate trong Python/import.c, sau đó tái hiện các ví dụ Case A và Case B từ issue trên Python 3.15. Được xem là hoàn tất khi lazy import a.b as c tuân theo ngữ nghĩa của eager import: nó import a.b theo tên, phát sinh lỗi đối với một subpath không phải là module và không sử dụng thuộc tính che khuất nó.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Đánh giá

Công nghệ
python
Lĩnh vực
backend
Loại issue
Lỗi
Độ khó
3/5
Thời gian dự kiến
1-2 ngày
Mức độ hoạt động
Sôi nổi
Độ rõ ràng
Đặc tả rõ ràng
Mức phù hợp với người mới
68/100

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.