Skip to content

04-导入机制

Python 3.11+

本章用一个完整的 myshop 项目贯穿所有示例,让每种导入方式都有具体的落地场景。


概念铺垫

为什么需要了解导入机制?

问题场景

你建了一个简单的商店项目,写下这样的导入:

python
# services/order_service.py

# 写法 A
from myshop.core.config import Config

# 写法 B
from ..core.config import Config

# 写法 C
import config

三种写法都想导入同一个 Config 类,但结果却完全不同:

  • 写法 A:在任何地方运行都正常,推荐
  • 写法 B:直接运行 python order_service.py 会报 ImportError
  • 写法 C:可能导入了项目根目录的另一个 config.py,而不是你想要的那个

不理解导入机制,就会出现"明明写了导入,却报找不到模块"的困惑。 本章解决这个问题。


章节导航

部分内容
第一部分绝对导入 — 完整路径,从项目根开始
第二部分相对导入 — 点号语法,在包内跳转
第三部分常见陷阱 — 三类高频错误及解法
L2 实践层推荐做法、反模式、常见陷阱、适用场景

本章项目:myshop

整个章节使用同一个项目结构,先建立清晰的全局视图:

myshop/                         ← 顶层包(有 __init__.py)
├── __init__.py
├── core/                       ← 核心配置子包
│   ├── __init__.py
│   └── config.py               ← Config 类
├── models/                     ← 数据模型子包
│   ├── __init__.py             ← 统一导出 User、Product
│   ├── user.py                 ← User 类
│   └── product.py              ← Product 类
└── services/                   ← 业务逻辑子包
    ├── __init__.py
    └── order_service.py        ← OrderService,依赖 Config、User、Product
main.py                         ← 项目入口,位于 myshop/ 同级
┌──────────────────────────────────────────────────────────────┐
│                     myshop 模块依赖关系                       │
├──────────────────────────────────────────────────────────────┤
│                                                              │
│   main.py                                                    │
│     └── myshop.services.order_service                        │
│             ├── myshop.core.config        (Config)           │
│             ├── myshop.models.user        (User)             │
│             └── myshop.models.product     (Product)          │
│                                                              │
│   myshop.models.__init__                                     │
│     ├── .user    (User)                                      │
│     └── .product (Product)                                   │
│                                                              │
└──────────────────────────────────────────────────────────────┘

L1 理解层:会用

第一部分:绝对导入

1.1 概念动机

绝对导入从项目顶层包开始,写出完整的点分路径。就像写快递地址时写"北京市朝阳区XX街XX号",无论你站在哪里,地址指向的地方是唯一的。

1.2 最简示例

python
# main.py(位于 myshop/ 同级目录)
from myshop.core.config import Config

print(Config.APP_NAME)  # MyShop

运行方式(在 myshop/ 的父目录执行):

bash
python main.py

1.3 详细讲解

绝对导入路径的构成规则:

┌──────────────────────────────────────────────────────────────┐
│                   绝对导入路径拆解                            │
├──────────────────────────────────────────────────────────────┤
│                                                              │
│   from myshop . core . config import Config                  │
│        ──┬──   ──┬──   ──┬──          ──┬──                  │
│          │       │       │              └─ 导入的名称         │
│          │       │       └──────────────── 模块文件名(.py)  │
│          │       └──────────────────────── 子包目录名         │
│          └──────────────────────────────── 顶层包目录名       │
│                                                              │
│   对应文件:myshop/core/config.py                            │
│                                                              │
└──────────────────────────────────────────────────────────────┘

三种绝对导入写法的区别:

python
# 写法 1:导入具体名称(推荐)
from myshop.core.config import Config
config = Config()               # 直接使用

# 写法 2:导入模块
from myshop.core import config
config = config.Config()        # 需要加模块前缀

# 写法 3:导入顶层包(不推荐,路径太长)
import myshop.core.config
config = myshop.core.config.Config()

1.4 渐进复杂化

myshop/core/config.py 的完整内容:

python
# myshop/core/config.py
from pathlib import Path
from typing import Final


class Config:
    """应用全局配置"""

    APP_NAME: Final[str] = "MyShop"
    DEBUG: Final[bool] = True
    BASE_DIR: Final[Path] = Path(__file__).parent.parent  # myshop/

    @classmethod
    def get_data_dir(cls) -> Path:
        """返回数据目录路径"""
        return cls.BASE_DIR / "data"

myshop/services/order_service.py 用绝对导入引用所有依赖:

python
# myshop/services/order_service.py
from myshop.core.config import Config
from myshop.models.user import User
from myshop.models.product import Product


class OrderService:
    """订单服务"""

    def create_order(self, user: User, products: list[Product]) -> dict:
        total = sum(p.price for p in products)
        return {
            "app": Config.APP_NAME,
            "user": user.name,
            "items": [p.name for p in products],
            "total": total,
        }

1.5 实际应用

main.py 作为入口,使用绝对导入调用整个项目:

python
# main.py
from myshop.models.user import User
from myshop.models.product import Product
from myshop.services.order_service import OrderService


def main() -> None:
    user = User(user_id=1, name="张三")
    products = [
        Product(product_id=101, name="笔记本", price=29.9),
        Product(product_id=102, name="钢笔", price=9.9),
    ]
    service = OrderService()
    order = service.create_order(user, products)
    print(order)
    # {'app': 'MyShop', 'user': '张三', 'items': ['笔记本', '钢笔'], 'total': 39.8}


if __name__ == "__main__":
    main()

关键代码说明:

代码含义为什么这样写
from myshop.core.config import Config从顶层包 myshop 开始写完整路径无论从哪个目录运行,路径含义不变
Path(__file__).parent.parent获取 config.py 向上两级的目录,即项目根__file__ 是当前文件路径,.parent 取父目录
if __name__ == "__main__"只在直接运行时执行 main()被其他模块导入时不会自动执行入口代码

第二部分:相对导入

2.1 概念动机

相对导入用点号(.)表示"从当前包出发"的相对位置,不用写包名。好处是:当你把整个 myshop/ 目录改名为 mystore/ 时,包内所有相对导入语句一行都不用改。

2.2 最简示例

python
# myshop/models/__init__.py
# 从当前包(models/)导入 user 和 product 模块
from .user import User
from .product import Product

这里的 .user 表示"当前目录(models/)下的 user 模块"。

2.3 详细讲解

┌──────────────────────────────────────────────────────────────┐
│                   相对导入点号含义                            │
├──────────────────────────────────────────────────────────────┤
│                                                              │
│   当前文件:myshop/services/order_service.py                 │
│                                                              │
│   .        = myshop/services/(当前包)                      │
│   ..        = myshop/(上级包)                              │
│   ..core    = myshop/core/(上级包下的 core 子包)            │
│                                                              │
│   from . import helpers         # myshop/services/helpers   │
│   from .. import core           # myshop/core               │
│   from ..core.config import Config  # myshop/core/config.py │
│   from ..models import User         # myshop/models/User    │
│                                                              │
└──────────────────────────────────────────────────────────────┘
符号含义services/order_service.py 中指向
.当前包myshop/services/
..上级包myshop/
...上两级包myshop/ 的父目录(通常不用)

2.4 渐进复杂化

用相对导入重写 order_service.py

python
# myshop/services/order_service.py(相对导入版本)
from ..core.config import Config        # .. → myshop/,再进 core/config
from ..models.user import User          # .. → myshop/,再进 models/user
from ..models.product import Product    # .. → myshop/,再进 models/product


class OrderService:
    def create_order(self, user: User, products: list[Product]) -> dict:
        total = sum(p.price for p in products)
        return {
            "app": Config.APP_NAME,
            "user": user.name,
            "items": [p.name for p in products],
            "total": total,
        }

models/__init__.py 用相对导入聚合子模块,让外部只需 from myshop.models import User

python
# myshop/models/__init__.py
from .user import User
from .product import Product

__all__ = ["User", "Product"]

2.5 实际应用

相对导入和绝对导入的完整对照(同一个项目,两种写法等价):

python
# ─── 绝对导入(推荐用于 main.py 和跨包调用)──────────────────
from myshop.core.config import Config
from myshop.models.user import User
from myshop.models.product import Product

# ─── 相对导入(推荐用于包内部文件)──────────────────────────
# 以下代码位于 myshop/services/order_service.py
from ..core.config import Config
from ..models.user import User
from ..models.product import Product

# ─── models/__init__.py 内部聚合──────────────────────────────
# 以下代码位于 myshop/models/__init__.py
from .user import User          # . 表示 models/ 自身
from .product import Product

关键代码说明:

代码含义为什么这样写
from .user import User从当前包(models/)的 user 模块导入包内聚合用相对导入,改包名时无需修改
from ..core.config import Config先上一级到 myshop/,再进 core/config跨子包访问需要先回到共同父包
__all__ = ["User", "Product"]声明 from myshop.models import * 时导出哪些名称控制公开接口,隐藏内部实现细节

第三部分:常见导入陷阱

3.1 陷阱一:相对导入不能直接运行

现象: 直接用 python 运行包含相对导入的文件时报错。

bash
# ❌ 直接运行包内模块
python myshop/services/order_service.py
# ImportError: attempted relative import with no known parent package

原因: 直接运行时,Python 把 order_service.py 当作顶层脚本,它不属于任何包,.. 无处可跳。

┌──────────────────────────────────────────────────────────────┐
│          直接运行 vs -m 运行 的区别                           │
├──────────────────────────────────────────────────────────────┤
│                                                              │
│  python myshop/services/order_service.py                     │
│    → __name__ = "__main__"                                   │
│    → __package__ = None    ← 没有包信息,相对导入失败        │
│                                                              │
│  python -m myshop.services.order_service                     │
│    → __name__ = "__main__"                                   │
│    → __package__ = "myshop.services"  ← 有包信息,相对导入正常│
│                                                              │
└──────────────────────────────────────────────────────────────┘

解决方案:

bash
# ✅ 用 -m 运行(在 myshop/ 的父目录执行)
python -m myshop.services.order_service

# ✅ 或把入口逻辑放在 main.py,用绝对导入
python main.py

结论:包内模块用相对导入,但只通过 main.py-m 执行,绝不直接运行包内文件。


3.2 陷阱二:循环导入

现象: user.pyproduct.py 互相引用,导入时报 ImportError 或获取到未初始化的空模块。

python
# ❌ 循环导入示例
# myshop/models/user.py
from myshop.models.product import Product   # user 导入 product

class User:
    def get_cart(self) -> list[Product]:
        return []

# myshop/models/product.py
from myshop.models.user import User         # product 又导入 user

class Product:
    def get_owner(self) -> User:
        return User(1, "")

# Python 执行顺序:
# 1. 开始导入 user.py
# 2. user.py 触发导入 product.py
# 3. product.py 触发导入 user.py —— 但 user.py 还没执行完!
# 4. User 类还不存在 → ImportError 或 AttributeError

解决方案:只在方法内部做延迟导入,配合 TYPE_CHECKING 保留类型提示。

python
# ✅ myshop/models/user.py(修复循环导入)
from __future__ import annotations
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    # 这段代码只在静态类型检查时执行,运行时跳过
    from myshop.models.product import Product


class User:
    def __init__(self, user_id: int, name: str) -> None:
        self.user_id = user_id
        self.name = name

    def get_cart(self) -> list[Product]:
        # 延迟导入:方法被调用时才导入,此时两个模块都已加载完毕
        from myshop.models.product import Product
        return []
python
# ✅ myshop/models/product.py(同样处理)
from __future__ import annotations
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from myshop.models.user import User


class Product:
    def __init__(self, product_id: int, name: str, price: float) -> None:
        self.product_id = product_id
        self.name = name
        self.price = price

    def get_owner(self) -> User:
        from myshop.models.user import User
        return User(0, "unknown")

关键代码说明:

代码含义为什么这样写
from __future__ import annotations让类型注解变成字符串,延迟求值注解里写 Product 时,Product 类可以还未加载
if TYPE_CHECKING:此块只在 mypy/pyright 检查时执行运行时跳过,避免循环导入;检查时能看到类型信息
方法内 from myshop.models.product import Product延迟到方法调用时才导入此时两个模块都已完整加载,循环不存在了

3.3 陷阱三:模块名遮蔽标准库

现象: 自定义文件名和标准库重名,导入到错误的模块。

python
# ❌ 在项目根目录创建了 email.py
# email.py(自定义)
def send(to: str, msg: str) -> None:
    print(f"发送给 {to}: {msg}")

# main.py
import email          # 导入的是 Python 标准库的 email 模块,不是自定义的!
email.send("a@b.com", "hello")  # AttributeError: module 'email' has no attribute 'send'

常见容易冲突的文件名:

危险文件名被遮蔽的标准库
email.pyemail(邮件解析)
os.pyos(操作系统接口)
re.pyre(正则表达式)
random.pyrandom(随机数)
test.pytest(Python 内部测试包)
types.pytypes(动态类型创建)

解决方案:给自定义模块起不与标准库冲突的名字。

python
# ✅ 重命名为 email_sender.py
# myshop/services/email_sender.py
def send(to: str, msg: str) -> None:
    print(f"发送给 {to}: {msg}")

# main.py
from myshop.services.email_sender import send
send("a@b.com", "hello")   # 正确导入

L2 实践层:用好

┌──────────────────────────────────────────────────────────────┐
│                   导入方式选择指南                            │
├──────────────────────────────────────────────────────────────┤
│                                                              │
│   绝对导入:                                                  │
│   ✓ main.py 等顶层入口文件                                   │
│   ✓ 跨包调用(services → models)                            │
│   ✓ 不确定时默认选绝对导入                                   │
│                                                              │
│   相对导入:                                                  │
│   ✓ 包内部文件之间(同一子包内)                             │
│   ✓ __init__.py 聚合子模块时                                 │
│                                                              │
│   绝不直接运行包内文件,始终用 main.py 或 python -m          │
│                                                              │
└──────────────────────────────────────────────────────────────┘

推荐做法

做法原因myshop 中的示例
入口文件用绝对导入清晰,不依赖运行位置main.pyfrom myshop.models.user import User
包内 __init__.py 用相对导入聚合改包名无需修改from .user import User
跨子包访问用绝对导入路径明确,IDE 跳转友好order_service.pyfrom myshop.core.config import Config
循环依赖用 TYPE_CHECKING + 延迟导入运行时无循环,类型检查有提示user.pyproduct.py 互相引用时
模块名不与标准库冲突避免遮蔽内置模块email_sender.py 而非 email.py
__all__ 声明公开接口明确哪些是公开 APImodels/__init__.py__all__ = ["User", "Product"]

导入顺序规范(每组之间空一行):

python
# myshop/services/order_service.py

# 第一组:标准库
from __future__ import annotations
from typing import TYPE_CHECKING

# 第二组:第三方库(本项目暂无)
# import requests

# 第三组:本地包(绝对导入)
from myshop.core.config import Config
from myshop.models.user import User
from myshop.models.product import Product

# 第四组:仅类型检查时的导入
if TYPE_CHECKING:
    pass  # 此处放会产生循环导入的类型

反模式:不要这样做

python
# ❌ 在顶层脚本使用相对导入
# main.py
from .models.user import User   # SyntaxError 或 ImportError

# ✅ 顶层脚本永远用绝对导入
from myshop.models.user import User

# ---

# ❌ 直接运行包内模块
# python myshop/services/order_service.py  → ImportError

# ✅ 用 -m 运行或通过 main.py 入口
# python -m myshop.services.order_service
# python main.py

# ---

# ❌ 用 import * 导入所有名称
from myshop.models import *     # 不清楚导入了什么,污染命名空间

# ✅ 明确导入需要的名称
from myshop.models import User, Product

# ---

# ❌ 把自定义文件命名为标准库同名
# os.py、re.py、email.py ...   → 遮蔽标准库

# ✅ 使用不会被冲突的名字
# file_utils.py、email_sender.py

# ---

# ❌ 在模块顶层互相导入(循环)
# user.py: from myshop.models.product import Product
# product.py: from myshop.models.user import User

# ✅ TYPE_CHECKING + 方法内延迟导入
# user.py:
#   from __future__ import annotations
#   from typing import TYPE_CHECKING
#   if TYPE_CHECKING:
#       from myshop.models.product import Product

常见陷阱

陷阱错误信息解决方案
直接运行含相对导入的文件ImportError: attempted relative import with no known parent packagepython -m 包名.模块名 或通过 main.py 入口运行
循环导入ImportErrorAttributeError: partially initialized moduleTYPE_CHECKING + 方法内延迟导入
模块名遮蔽标准库AttributeError: module 'xxx' has no attribute 'yyy'给自定义模块起不冲突的名字
__init__.py 导入顺序错误ImportError: cannot import name 'X'把被依赖的模块放在依赖它的模块前面导入
运行目录不在 Python 路径中ModuleNotFoundError: No module named 'myshop'确保在项目根目录(myshop/ 的父目录)运行

适用场景

场景推荐方式原因
main.py 调用各子包✅ 绝对导入位置独立,语义清晰
models/__init__.py 聚合 UserProduct✅ 相对导入(.user.product包改名无需修改
services/ 访问 core/ 的配置✅ 绝对导入跨子包,路径更清晰
user.py 需要引用 Product 做类型注解TYPE_CHECKING + from __future__ import annotations避免循环导入
包改名/迁移✅ 包内用相对导入只改顶层包名
不确定选择哪种✅ 绝对导入最安全,不会出错

L3 专家层:深入

Python 如何实现:importlib 内部架构

┌──────────────────────────────────────────────────────────────┐
│              importlib 导入系统核心架构                        │
├──────────────────────────────────────────────────────────────┤
│                                                              │
│   importlib.__import__(name)                                 │
│     │                                                        │
│     ├── 1. 检查 sys.modules[name](缓存)                    │
│     │      └── 命中 → 直接返回                              │
│     │                                                        │
│     ├── 2. 遍历 sys.meta_path(Finder 链)                   │
│     │      │                                                 │
│     │      ├── BuiltinImporter                               │
│     │      │   └── 处理 sys.builtin_module_names 中的模块   │
│     │      │      例如: sys, builtins, errno               │
│     │      │                                                 │
│     │      ├── FrozenImporter                                │
│     │      │   └── 处理冻结模块(_frozen_importlib 自身)   │
│     │      │                                                 │
│     │      └── PathFinder                                    │
│     │          │                                             │
│     │          ├── 遍历 sys.path_hooks                      │
│     │          │   ├── zipimport.zipimporter                │
│     │          │   │   └── 处理 .zip 文件中的模块           │
│     │          │   └── FileFinder                           │
│     │          │       └── 处理文件系统中的模块             │
│     │          │                                             │
│     │          └── 对 sys.path 每个目录:                   │
│     │              ├── 查找 <dir>/<name>.py                  │
│     │              ├── 查找 <dir>/<name>/__init__.py (包)    │
│     │              └── 查找 <dir>/<name>.pyc                 │
│     │                                                        │
│     ├── 3. Finder.find_spec(name) → ModuleSpec               │
│     │      ModuleSpec 包含:                                 │
│     │      - name: 模块名                                   │
│     │      - loader: Loader 对象                            │
│     │      - origin: 源码路径                               │
│     │      - submodule_search_locations: 子模块搜索路径     │
│     │                                                        │
│     ├── 4. Loader.create_module(spec) → 创建空模块对象      │
│     │                                                        │
│     ├── 5. Loader.exec_module(module)                        │
│     │      ├── 编译源码为 CodeObject                        │
│     │      └── exec(code, module.__dict__)                  │
│     │                                                        │
│     └── 6. sys.modules[name] = module(写入缓存)           │
│                                                              │
└──────────────────────────────────────────────────────────────┘

验证 Finder / Loader 链:

python
import sys
import importlib
import importlib.abc

# 1. 查看 sys.meta_path 上的所有 Finder
print("=== sys.meta_path ===")
for i, finder in enumerate(sys.meta_path):
    print(f"[{i}] {type(finder).__module__}.{type(finder).__qualname__}")
# 输出示例:
# [0] _frozen_importlib.BuiltinImporter
# [1] _frozen_importlib.FrozenImporter
# [2] _frozen_importlib_external.PathFinder

# 2. 查看 PathFinder 内部的 path_hooks
print("\n=== sys.path_hooks ===")
for i, hook in enumerate(sys.path_hooks):
    print(f"[{i}] {hook}")
# 输出示例:
# [0] <class 'zipimport.zipimporter'>
# [1] <function FileFinder.path_hook.<locals>.path_hook_for_FileFinder at ...>

# 3. 查找指定模块的 spec
for name in ["sys", "json", "math"]:
    spec = importlib.util.find_spec(name)
    if spec:
        print(f"\n=== {name} Spec ===")
        print(f"  loader: {type(spec.loader).__qualname__}")
        print(f"  origin: {spec.origin}")
        print(f"  submodule_search_locations: {spec.submodule_search_locations}")
        print(f"  has_location: {spec.has_location}")

自定义导入钩子

可以利用 sys.meta_pathsys.path_hooks 添加自定义导入逻辑:

python
import sys
import importlib.abc
import importlib.machinery


class DebugFinder(importlib.abc.MetaPathFinder):
    """在控制台打印每个 import 请求的调试 Finder"""

    def find_spec(self, fullname, path, target=None):
        print(f"[DebugFinder] 查找模块: {fullname!r}, path={path}")
        return None  # 返回 None 表示不处理,交由下一个 Finder


# 注册自定义 Finder
sys.meta_path.insert(0, DebugFinder())

# 测试:每次 import 都会触发打印
import math  # 输出:[DebugFinder] 查找模块: 'math', path=None
import json  # 输出:[DebugFinder] 查找模块: 'json', path=None

# 查看导入后的 sys.modules
print(f"\nmath 在 sys.modules 中: {'math' in sys.modules}")
print(f"sys.modules 总数: {len(sys.modules)}")

自定义 URL 导入器示例

python
import sys
import importlib.abc
import importlib.machinery
import types


class StringLoader(importlib.abc.Loader):
    """从字符串加载模块的 Loader"""

    def __init__(self, code: str):
        self.code = code

    def exec_module(self, module: types.ModuleType) -> None:
        exec(self.code, module.__dict__)


class StringFinder(importlib.abc.MetaPathFinder):
    """查找以 'virtual_' 开头的模块,从预定义字符串提供"""

    _modules = {
        "virtual_config": """
SECRET_KEY = "abc123"
DEBUG = True
""",
    }

    def find_spec(self, fullname, path, target=None):
        if fullname in self._modules:
            loader = StringLoader(self._modules[fullname])
            return importlib.machinery.ModuleSpec(
                fullname, loader, origin=f"<string:{fullname}>"
            )
        return None


# 注册并测试
sys.meta_path.insert(0, StringFinder())
import virtual_config
print(virtual_config.SECRET_KEY)  # abc123
print(virtual_config.DEBUG)       # True

性能考量

操作复杂度说明
导入已缓存模块O(1) 哈希查找sys.modules 是普通 dict,查找极快
PathFinder 查找模块O(p) 最坏p = sys.path 条目数,找到即停
首次编译源码O(源码大小)解析 + 编译为字节码,写入 .pyc
执行模块代码O(模块复杂度)取决于顶层代码量
相对导入解析O(1)点号计数 → 确定跳转层级
TYPE_CHECKING 跳过O(0)运行时完全跳过 if 块
方法内延迟导入O(1) 首次检查 + O(导入)首次调用时触发导入

知识关联

┌──────────────────────────────────────────────────────────────┐
│                    导入机制 知识关联图                         │
├──────────────────────────────────────────────────────────────┤
│                                                              │
│   本章(导入机制)                                           │
│     │                                                        │
│     ├── 绝对导入 ───────────► 03-包的结构(包的组织)        │
│     │                                                        │
│     ├── 相对导入 ───────────► 03-包的结构(__init__.py)    │
│     │                                                        │
│     ├── sys.meta_path ──────► 01-模块基础(import 流程)     │
│     │                                                        │
│     ├── Finder / Loader ────► 01-模块基础(sys.modules)     │
│     │                                                        │
│     ├── 循环导入解决 ───────► 02-自定义模块(懒加载)        │
│     │                                                        │
│     ├── -m 运行机制 ────────► 01-模块基础(__name__)       │
│     │                                                        │
│     └── 自定义 Import Hook ─► 进阶:插件系统、虚拟文件系统   │
│                                                              │
└──────────────────────────────────────────────────────────────┘

本章小结

┌──────────────────────────────────────────────────────────────┐
│                    导入机制 知识要点                          │
├──────────────────────────────────────────────────────────────┤
│                                                              │
│   绝对导入:from myshop.core.config import Config            │
│   ✓ 从顶层包名开始写完整路径                                 │
│   ✓ 任何地方运行都正确,IDE 支持好                           │
│   ✓ 入口文件和跨包调用的首选                                 │
│                                                              │
│   相对导入:from ..core.config import Config                  │
│   ✓ 点号表示相对当前包的位置                                 │
│   ✓ 只能在包内部使用,不能在顶层脚本用                       │
│   ✓ 包内聚合和同包模块互引时使用                             │
│                                                              │
│   三类常见陷阱:                                             │
│   ✓ 直接运行包内文件 → 改用 -m 或 main.py                   │
│   ✓ 循环导入 → TYPE_CHECKING + 延迟导入                     │
│   ✓ 模块名遮蔽标准库 → 避免与内置模块同名                   │
│                                                              │
└──────────────────────────────────────────────────────────────┘