← 返回 Day 14 正文

Day 14 · 图解

模块与包图解

本文档包含 Day 014 内容的 ASCII 图和 Mermaid 图。


1. import 搜索路径流程图

Mermaid 流程图

flowchart TD START(["执行 import mymodule"]) --> CHECK_CACHE{"sys.modules 中\n是否已缓存?"} CHECK_CACHE -->|"是(已导入过)"| DONE["✅ 直接使用缓存对象\n不重新执行"] CHECK_CACHE -->|"否"| SEARCH["在 sys.path 中遍历查找"] SEARCH --> FOUND{"找到 mymodule.py\n或 mymodule/ 包?"} FOUND -->|"是"| COMPILE["编译 .py → .pyc 字节码\n(若已存在且版本匹配则跳过)"] FOUND -->|"否"| MODULE_NAME{"模块名是否含\n点号(子模块)?"} MODULE_NAME -->|"是"| PARENT_CHECK{"父包已导入?"} MODULE_NAME -->|"否"| NOT_FOUND["❌ ModuleNotFoundError"] PARENT_CHECK -->|"否"| SEARCH PARENT_CHECK -->|"是"| COMPILE COMPILE --> EXECUTE["创建新 module 对象\n在模块命名空间中执行代码"] EXECUTE --> INSERT_CACHE["将模块存入 sys.modules"] INSERT_CACHE --> BIND["将模块名绑定到当前作用域"] BIND --> DONE
flowchart TD
    START(["执行 import mymodule"]) 
    --> CHECK_CACHE{"sys.modules 中\n是否已缓存?"}
    
    CHECK_CACHE -->|"是(已导入过)"| DONE["✅ 直接使用缓存对象\n不重新执行"]
    CHECK_CACHE -->|"否"| SEARCH["在 sys.path 中遍历查找"]
    
    SEARCH --> FOUND{"找到 mymodule.py\n或 mymodule/ 包?"}
    
    FOUND -->|"是"| COMPILE["编译 .py → .pyc 字节码\n(若已存在且版本匹配则跳过)"]
    FOUND -->|"否"| MODULE_NAME{"模块名是否含\n点号(子模块)?"}
    
    MODULE_NAME -->|"是"| PARENT_CHECK{"父包已导入?"}
    MODULE_NAME -->|"否"| NOT_FOUND["❌ ModuleNotFoundError"]
    
    PARENT_CHECK -->|"否"| SEARCH
    PARENT_CHECK -->|"是"| COMPILE
    
    COMPILE --> EXECUTE["创建新 module 对象\n在模块命名空间中执行代码"]
    EXECUTE --> INSERT_CACHE["将模块存入 sys.modules"]
    INSERT_CACHE --> BIND["将模块名绑定到当前作用域"]
    BIND --> DONE

ASCII 搜索路径

┌─────────────────────────────────────────────────────┐
│              Python 模块搜索路径 (sys.path)           │
├─────────────────────────────────────────────────────┤
│                                                      │
│  ① 当前脚本所在目录 (入口脚本)                         │
│     sys.path[0]  = /home/user/myproject               │
│                                                      │
│  ② PYTHONPATH 环境变量 (如有设置)                     │
│     export PYTHONPATH=/custom/libs:$PYTHONPATH        │
│                                                      │
│  ③ 标准库路径                                        │
│     /usr/lib/python3.12/                              │
│     /usr/lib/python3.12/lib-dynload/                  │
│                                                      │
│  ④ site-packages (第三方包)                           │
│     /usr/lib/python3.12/site-packages/                │
│                                                      │
└─────────────────────────────────────────────────────┘

2. name 值变化示意

name 在直接运行 vs 被导入时的值

┌─────────────────────────────────────────────────────────┐
│                                                          │
│  python3 mymodule.py                    import mymodule  │
│                                                          │
│  ┌─────────────────┐                  ┌───────────────┐  │
│  │  __name__ 被赋值  │                  │ __name__ 被赋值│  │
│  │  '__main__'      │                  │ 'mymodule'    │  │
│  └────────┬────────┘                  └──────┬────────┘  │
│           │                                   │           │
│           ▼                                   ▼           │
│  ┌─────────────────┐                  ┌───────────────┐  │
│  │ 执行全部代码     │                  │ 执行全部代码   │  │
│  │                  │                  │               │  │
│  │ if __name__ ==   │                  │ if __name__   │  │
│  │    '__main__':   │                  │    ==         │  │
│  │    → True        │                  │    '__main__':│  │
│  │    → 执行测试代码 │                  │    → False    │  │
│  │    → 交互演示     │                  │    → 跳过     │  │
│  └─────────────────┘                  └───────────────┘  │
│                                                          │
└─────────────────────────────────────────────────────────┘

不同场景下的 name 值

文件 运行方式 __name__
main.py python3 main.py '__main__'
mymodule.py python3 mymodule.py '__main__'
mymodule.py import mymodule 'mymodule'
package/module.py import package.module 'package.module'
__main__.py python3 -m package '__main__'

Mermaid 状态图

stateDiagram-v2 state "python3 script.py" as DirectRun state "import script" as Import DirectRun --> SetMain: 解释器赋值 SetMain: __name__ = '__main__' SetMain --> ExecuteAll: 执行所有代码 ExecuteAll --> TestCode: if __name__ == '__main__' → True TestCode --> RunTest: ✅ 运行测试/演示代码 Import --> SetName: 解释器赋值 SetName: __name__ = 'script' SetName --> ExecuteCode: 执行模块代码 ExecuteCode --> SkipTest: if __name__ == '__main__' → False SkipTest --> ReturnModule: 跳过测试代码 ReturnModule --> UserCode: 返回模块对象给调用方 RunTest --> Output: 打印结果/执行交互
stateDiagram-v2
    state "python3 script.py" as DirectRun
    state "import script" as Import
    
    DirectRun --> SetMain: 解释器赋值
    SetMain: __name__ = '__main__'
    SetMain --> ExecuteAll: 执行所有代码
    ExecuteAll --> TestCode: if __name__ == '__main__' → True
    TestCode --> RunTest: ✅ 运行测试/演示代码
    
    Import --> SetName: 解释器赋值
    SetName: __name__ = 'script'
    SetName --> ExecuteCode: 执行模块代码
    ExecuteCode --> SkipTest: if __name__ == '__main__' → False
    SkipTest --> ReturnModule: 跳过测试代码
    
    ReturnModule --> UserCode: 返回模块对象给调用方
    RunTest --> Output: 打印结果/执行交互

3. 包目录结构图

完整包结构

project/
├── main.py                          ← 入口文件
│
├── strutils/                        ← 字符串工具包
│   ├── __init__.py                  ← 包初始化,集中导入/重导出
│   ├── manipulation.py              ← 字符串操作:reverse, truncate
│   ├── validation.py                ← 字符串验证:is_email, is_phone
│   └── formatting.py                ← 字符串格式化:to_title_case
│
├── mathutils/                       ← 数学工具包
│   ├── __init__.py                  ← 包初始化,集中导入/重导出
│   ├── basic.py                     ← 基础运算:add, subtract
│   ├── statistics.py                ← 统计计算:mean, median, variance
│   └── conversion.py                ← 单位转换:celsius_to_fahrenheit
│
├── config.py                        ← 配置文件
│
└── tests/                           ← 测试目录
    └── test_strutils.py

Mermaid 树状结构

graph TB main["main.py<br/>(入口文件,绝对导入)"] subgraph strutils_pkg["strutils/"] strutils_init["__init__.py<br/>🎯 导入并重导出"] strutils_manip["manipulation.py<br/>reverse, truncate"] strutils_vali["validation.py<br/>is_email, is_phone<br/>📍 相对导入 .formatting"] strutils_form["formatting.py<br/>to_title_case, to_snake_case"] end subgraph mathutils_pkg["mathutils/"] mathutils_init["__init__.py<br/>🎯 导入并重导出"] mathutils_basic["basic.py<br/>add, subtract, factorial<br/>gcd, lcm"] mathutils_stat["statistics.py<br/>mean, median, variance<br/>📍 相对导入 .basic"] mathutils_conv["conversion.py<br/>celsius_to_fahrenheit<br/>kg_to_lbs"] end config["config.py"] main --> strutils_pkg main --> mathutils_pkg main --> config strutils_vali -.->|from .formatting| strutils_form mathutils_stat -.->|from .basic| mathutils_basic style main fill:#ffd43b style strutils_pkg fill:#e1f5fe style mathutils_pkg fill:#fce4ec
graph TB
    main["main.py<br/>(入口文件,绝对导入)"]
    
    subgraph strutils_pkg["strutils/"]
        strutils_init["__init__.py<br/>🎯 导入并重导出"]
        strutils_manip["manipulation.py<br/>reverse, truncate"]
        strutils_vali["validation.py<br/>is_email, is_phone<br/>📍 相对导入 .formatting"]
        strutils_form["formatting.py<br/>to_title_case, to_snake_case"]
    end
    
    subgraph mathutils_pkg["mathutils/"]
        mathutils_init["__init__.py<br/>🎯 导入并重导出"]
        mathutils_basic["basic.py<br/>add, subtract, factorial<br/>gcd, lcm"]
        mathutils_stat["statistics.py<br/>mean, median, variance<br/>📍 相对导入 .basic"]
        mathutils_conv["conversion.py<br/>celsius_to_fahrenheit<br/>kg_to_lbs"]
    end
    
    config["config.py"]
    
    main --> strutils_pkg
    main --> mathutils_pkg
    main --> config
    
    strutils_vali -.->|from .formatting| strutils_form
    mathutils_stat -.->|from .basic| mathutils_basic
    
    style main fill:#ffd43b
    style strutils_pkg fill:#e1f5fe
    style mathutils_pkg fill:#fce4ec

4. import 语句各种写法的关系

                    import 语句的分类
                    ┌─────────────────────────────────┐
                    │                                 │
    ┌───────────────┴───────────────┐                 │
    │                               │                 │
绝对导入                           相对导入            │
(absolute import)                 (relative import)  │
    │                               │                 │
    ▼                               ▼                 │
┌─────────┐                    ┌──────────┐          │
│明确指定│                    │基于当前 │          │
│完整路径│                    │模块位置 │          │
└────┬────┘                    └────┬─────┘          │
     │                              │                 │
     ▼                              ▼                 │
┌─────────────────┐        ┌─────────────────┐       │
│ import os        │        │ from . import x  │       │
│ from os import   │        │ from .. import y │       │
│   path           │        │ from .module    │       │
│ from package    │        │   import z      │       │
│   import module  │        └─────────────────┘       │
│ import numpy as │                                   │
│   np             │        ⚠ 不能在 __main__ 使用    │
└─────────────────┘                                   │
                                                      │
        ★ PEP 8 推荐优先使用绝对导入 ★               │
└─────────────────────────────────────────────────────┘

5. sys.modules 缓存机制

第一次 import mymodule:

    import mymodule
         │
         ▼
    ┌──────────────────┐    未命中      ┌──────────────────┐
    │  sys.modules     │ ──────────→   │  在 sys.path     │
    │  检查 'mymodule' │                │  中搜索文件       │
    └──────────────────┘               └────────┬─────────┘
                                               │
                                               ▼
                                        ┌──────────────────┐
                                        │  找到并编译 .py   │
                                        └────────┬─────────┘
                                               │
                                               ▼
                                        ┌──────────────────┐
                                        │  创建 module 对象 │
                                        │  执行模块代码     │
                                        └────────┬─────────┘
                                               │
                                               ▼
                                        ┌──────────────────┐
                                        │  sys.modules     │
                                        │  ['mymodule']    │
                                        │  存入缓存        │
                                        └──────────────────┘

第二次 import mymodule:

    import mymodule
         │
         ▼
    ┌──────────────────┐    命中!
    │  sys.modules     │ ──────────→   ✅ 直接返回缓存的对象
    │  检查 'mymodule' │
    └──────────────────┘

6. 模块生命周期

时间线 ──────────────────────────────────────────────────────→

模块源文件 (.py)     ██████████████████████████████████████
                     │(创建)                   (修改/删除)

字节码缓存 (.pyc)    ██████████████████████████████████████
                     │(首次导入生成)        (重新编译更新)

模块对象 (内存)      ────████████████████████──────────────
                        │                    │
                   (第一次导入)          (所有引用释放
                                         或 del sys.modules)

命名空间绑定         ──████████████████████████────────────
                     │                                  │
                (import 语句)                  (函数返回/作用域结束)

模块作用域中的变量   ██████████████████████████████████████
                     只要模块对象在 sys.modules 中,变量就存活

7. 实战工具包架构图

graph LR subgraph User["用户代码"] U1["import strutils"] U2["from mathutils import mean"] U3["from strutils.manipulation import truncate"] end subgraph strutils_pkg["strutils 包"] S1["__init__.py<br/>重导出核心函数"] S2["manipulation.py"] S3["validation.py"] S4["formatting.py"] end subgraph mathutils_pkg["mathutils 包"] M1["__init__.py<br/>重导出核心函数"] M2["basic.py"] M3["statistics.py"] M4["conversion.py"] end U1 --> S1 S1 --> S2 S1 --> S3 S1 --> S4 S3 -.->|相对导入| S4 U2 --> M1 U3 --> S2 M1 --> M2 M1 --> M3 M1 --> M4 M3 -.->|相对导入| M2
graph LR
    subgraph User["用户代码"]
        U1["import strutils"]
        U2["from mathutils import mean"]
        U3["from strutils.manipulation import truncate"]
    end
    
    subgraph strutils_pkg["strutils 包"]
        S1["__init__.py<br/>重导出核心函数"]
        S2["manipulation.py"]
        S3["validation.py"]
        S4["formatting.py"]
    end
    
    subgraph mathutils_pkg["mathutils 包"]
        M1["__init__.py<br/>重导出核心函数"]
        M2["basic.py"]
        M3["statistics.py"]
        M4["conversion.py"]
    end
    
    U1 --> S1
    S1 --> S2
    S1 --> S3
    S1 --> S4
    S3 -.->|相对导入| S4
    
    U2 --> M1
    U3 --> S2
    
    M1 --> M2
    M1 --> M3
    M1 --> M4
    M3 -.->|相对导入| M2

8. name 与 main 的关系总结

┌──────────────────────────────────────────────────────────────┐
│                                                               │
│  每个 .py 文件在运行时都有一个 __name__ 变量                    │
│                                                               │
│       ┌───────────────┐                                        │
│       │  运行方式      │                                        │
│       └───────┬───────┘                                        │
│               │                                                │
│     ┌─────────┴─────────┐                                      │
│     ▼                   ▼                                      │
│ ┌──────────┐      ┌──────────┐                                 │
│ │直接运行   │      │ 被导入    │                                 │
│ │python3 x │      │ import x │                                 │
│ └─────┬────┘      └─────┬────┘                                 │
│       │                 │                                       │
│       ▼                 ▼                                       │
│ ┌──────────┐      ┌──────────┐                                 │
│ │__name__  │      │__name__  │                                 │
│ │=         │      │= 'x'     │                                 │
│ │'__main__'│      │          │                                 │
│ └──────────┘      └──────────┘                                 │
│                                                               │
└──────────────────────────────────────────────────────────────┘

9. 循环导入示意图

❌ 循环导入问题:

    module_a.py              module_b.py
    ┌────────────────┐       ┌────────────────┐
    │ from module_b  │       │ from module_a  │
    │   import func_b│◄─────►│   import func_a│
    │                │       │                │
    │ def func_a():  │       │ def func_b():  │
    │   return       │       │   return       │
    │   func_b()     │       │   func_a()     │
    └────────────────┘       └────────────────┘
              │                      │
              └──────────────────────┘
        互相引用,造成循环依赖!
        
✅ 解决方案 1:延迟导入(在函数体内部导入)

    module_a.py              module_b.py
    ┌────────────────┐       ┌────────────────┐
    │ def func_a():  │       │ def func_b():  │
    │   from module_b│       │   from module_a│
    │     import     │       │     import     │
    │     func_b     │       │     func_a     │
    │   return       │       │   return       │
    │   func_b()     │       │   func_a()     │
    └────────────────┘       └────────────────┘

✅ 解决方案 2:提取公共模块

    module_a.py ──→ common.py ←── module_b.py
                      │
                  共享的函数/变量

10. sys.path 构成与修改

sys.path 是一个列表,决定了 Python 去哪里找模块:

>>> import sys
>>> sys.path
[
    '/home/user/project',               # ① 脚本所在目录
    '/usr/lib/python3.12',               # ② 标准库
    '/usr/lib/python3.12/lib-dynload',   # ③ C 扩展
    '/usr/lib/python3.12/site-packages',  # ④ 第三方包
]

可以动态修改(但谨慎!):
>>> sys.path.append('/my/custom/libs')   # 临时添加
>>> sys.path.insert(0, '/override/first') # 插入到最前面(优先级最高)
>>> sys.path.remove('/my/custom/libs')    # 移除

影响 sys.path 的来源:
┌────────────────┐
│ ① __file__ 的    │── 自动设置 sys.path[0] 为入口文件目录
│    所在目录      │
├────────────────┤
│ ② PYTHONPATH    │── 环境变量,多个路径用 : 分隔
│    环境变量      │
├────────────────┤
│ ③ .pth 文件     │── site-packages 中的文本文件
│                 │   每个路径一行,自动追加到 sys.path
├────────────────┤
│ ④ site 模块     │── 启动时自动执行,初始化 site-packages
│    自动初始化    │
└────────────────┘