Skip to content

1.4 模块与包管理

引言:组织代码的艺术

小白理解:模块和包就像整理房间

想象你的衣柜(项目):

  • 不整理:所有衣服堆在一起,找东西要翻半天
  • 用抽屉:上衣一个抽屉、裤子一个抽屉、袜子一个抽屉

在 Python 中:

  • 不整理:所有代码写在一个文件,几千行代码找东西找死
  • 用模块:API 调用放 api.py、工具放 tools.py、配置放 config.py

模块 = 抽屉,包 = 衣柜!

想象你正在构建一个复杂的 AI Agent 系统,包含:

  • LLM API 调用模块
  • 工具(Tools)模块
  • 提示词管理模块
  • 数据处理模块
  • 日志和监控模块

如果把所有代码都写在一个文件里,那将是维护的噩梦。这就是为什么我们需要模块化——将代码组织成独立的、可复用的模块。

学习目标

  • ✅ 理解模块(Module)和包(Package)的区别
  • ✅ 掌握 import 语句的各种形式
  • ✅ 理解 __name__ 和 __main__ 的作用
  • ✅ 构建规范的项目目录结构
  • ✅ 实战:组织 Agent 项目代码

第一部分:模块基础

什么是模块?

模块就是一个包含 Python 代码的 .py 文件。

小白理解

  • 你写了一个文件 math_utils.py
  • 这个文件就是一个"模块"
  • 其他文件可以"导入"这个模块,使用里面的函数

就像借工具:你有一套螺丝刀(模块),别人需要用的时候可以借(import)

创建 math_utils.py

python
"""数学工具模块"""

def add(a: int, b: int) -> int:
    """加法"""
    return a + b

def multiply(a: int, b: int) -> int:
    """乘法"""
    return a * b

PI = 3.14159

使用模块:

python
# main.py
import math_utils

result = math_utils.add(3, 5)
print(result)  # 8
print(math_utils.PI)  # 3.14159

第二部分:import 的多种形式

小白理解 - import 的各种写法

假设你要用别人的工具箱 math_utils

写法意思用法
import math_utils借整个工具箱math_utils.add(3, 5)
import math_utils as mu借工具箱,起个外号mu.add(3, 5)
from math_utils import add只借螺丝刀add(3, 5)
from math_utils import *把工具箱里的都拿出来add(3, 5) ⚠️ 不推荐

1. 基本 import

python
import math_utils

result = math_utils.add(3, 5)

2. import as(别名)

python
import math_utils as mu

result = mu.add(3, 5)

3. from import(导入特定项)

python
from math_utils import add, PI

result = add(3, 5)
print(PI)

4. from import as

python
from math_utils import add as addition

result = addition(3, 5)

5. from import *(不推荐!)

python
from math_utils import *

# 问题:不知道导入了什么,可能产生命名冲突
result = add(3, 5)

⚠️ 最佳实践

  • ✅ 明确导入:from module import specific_function
  • ❌ 避免:from module import *

第三部分:包(Package)

什么是包?

是包含 __init__.py 文件的目录,可以包含多个模块。

小白理解 - 模块 vs 包

概念是什么类比
模块一个 .py 文件一个抽屉
一个文件夹(含 __init__.py一个衣柜(有多个抽屉)
模块:math_utils.py          → 一个抽屉

包:
my_agent/                    → 一个衣柜
├── __init__.py              → 衣柜的"门"(必须有)
├── llm.py                   → 抽屉1
├── tools.py                 → 抽屉2
└── utils/                   → 衣柜里还有个小柜子
    ├── __init__.py
    └── text.py

项目结构:

my_agent/
├── __init__.py
├── llm.py
├── tools.py
└── utils/
    ├── __init__.py
    ├── text.py
    └── logging.py

__init__.py 的作用

小白理解 - __init__.py 是什么?

__init__.py 就像衣柜的**"门"和"目录"**:

  • 没有它:Python 不认为这是一个包,没法 import
  • 有了它:Python 知道"这是一个包,可以导入"

更重要的是,它还能控制对外暴露什么

python
# 用户只需要写:
from my_agent import call_llm

# 而不是:
from my_agent.llm.openai_client import call_llm  # 太长了!
python
# my_agent/__init__.py
"""AI Agent 包"""

from .llm import call_llm
from .tools import search_web, calculate

__version__ = "0.1.0"
__all__ = ["call_llm", "search_web", "calculate"]

使用包:

python
import my_agent

# 可以直接使用
result = my_agent.call_llm("Hello")

# 或者
from my_agent import search_web

第四部分:相对导入 vs 绝对导入

绝对导入(推荐)

python
# my_agent/tools.py
from my_agent.utils.text import clean_text
from my_agent.llm import call_llm

相对导入

python
# my_agent/tools.py
from .utils.text import clean_text  # 当前包的 utils.text
from .llm import call_llm            # 当前包的 llm
from ..other_package import something  # 上级包

💡 最佳实践

  • 包内部使用相对导入
  • 跨包使用绝对导入

第五部分:__name____main__

小白理解 - 这是 Python 最常见的"魔法"之一!

你经常会看到这样的代码:

python
if __name__ == "__main__":
    main()

这是什么意思?简单说:只有直接运行这个文件时,才执行下面的代码

类比:想象你有一个瑞士军刀

  • 直接用(直接运行):展开所有工具
  • 借给别人(被 import):只把需要的工具借出去

if __name__ == "__main__": 就是判断"我是被直接用,还是被借出去"

理解 __name__ 变量

python
# my_module.py
print(f"__name__ 的值: {__name__}")

def main():
    print("执行主函数")

if __name__ == "__main__":
    main()

运行效果

bash
# 直接运行
$ python my_module.py
__name__ 的值: __main__
执行主函数

# 作为模块导入
$ python
>>> import my_module
__name__ 的值: my_module

💡 应用场景

  • 模块既可以被导入使用
  • 也可以直接运行测试

第六部分:实战 - Agent 项目结构

标准项目结构

ai_agent_project/

├── pyproject.toml          # Poetry 配置
├── README.md               # 项目说明
├── .gitignore             # Git 忽略文件
├── .env                   # 环境变量(不提交)

├── src/                   # 源代码目录
│   └── ai_agent/
│       ├── __init__.py
│       ├── agent.py       # Agent 主逻辑
│       ├── llm/           # LLM 模块
│       │   ├── __init__.py
│       │   ├── openai_client.py
│       │   └── anthropic_client.py
│       ├── tools/         # Tools 模块
│       │   ├── __init__.py
│       │   ├── web_search.py
│       │   └── calculator.py
│       ├── prompts/       # 提示词模块
│       │   ├── __init__.py
│       │   └── templates.py
│       └── utils/         # 工具函数
│           ├── __init__.py
│           ├── logging.py
│           └── config.py

├── tests/                 # 测试目录
│   ├── __init__.py
│   ├── test_agent.py
│   └── test_tools.py

├── examples/              # 示例脚本
│   └── basic_usage.py

└── docs/                  # 文档
    └── api.md

实现示例

1. llm/openai_client.py

python
"""OpenAI API 客户端"""

from typing import Optional
import os

def call_openai(
    prompt: str,
    model: str = "gpt-3.5-turbo",
    api_key: Optional[str] = None
) -> str:
    """调用 OpenAI API"""
    api_key = api_key or os.getenv("OPENAI_API_KEY")
    # 实际实现...
    return f"[OpenAI响应] {prompt}"

2. tools/web_search.py

python
"""网络搜索工具"""

def search_web(query: str) -> str:
    """搜索网络"""
    # 实际实现会调用搜索 API
    return f"搜索结果: {query}"

3. __init__.py

python
"""AI Agent 包"""

from .llm.openai_client import call_openai
from .tools.web_search import search_web

__version__ = "0.1.0"
__all__ = ["call_openai", "search_web"]

4. 使用

python
# examples/basic_usage.py
from ai_agent import call_openai, search_web

if __name__ == "__main__":
    response = call_openai("What is AI?")
    print(response)
    
    results = search_web("Python tutorials")
    print(results)

本节总结

核心概念

  1. 模块 = .py 文件
  2. = 包含 __init__.py 的目录
  3. 绝对导入 vs 相对导入
  4. if __name__ == "__main__" 模式
  5. 规范的项目结构

核心概念一览表

概念一句话解释生活比喻
模块一个 .py 文件一个抽屉
包含 __init__.py 的文件夹一个衣柜
import导入模块使用其功能借工具
__init__.py包的入口文件衣柜的门和目录
__name__模块的名字身份证
__main__直接运行时的特殊名字"我是本人"
__all__控制 from x import * 导出什么清单

新手常见问题

Q1:为什么 import * 不推荐?

  • 不知道导入了什么,可能和你的变量名冲突
  • 代码可读性差,别人不知道函数从哪来的

Q2:__init__.py 可以是空的吗?

  • 可以!空文件也能让 Python 识别这是一个包
  • 但通常会在里面写一些初始化代码或导出公共 API

Q3:相对导入 ... 是什么意思?

  • . = 当前包(同级目录)
  • .. = 上级包(父目录)
  • ... = 上上级包

最佳实践

  • ✅ 使用明确的 import
  • ✅ 组织清晰的目录结构
  • ✅ 在 __init__.py 中暴露公共 API
  • ✅ 使用 __all__ 控制导出
  • ✅ 模块和包名使用小写+下划线

下一节:1.5 实战:构建第一个 LangChain Tool

基于 MIT 许可证发布。内容版权归作者所有。