跳至正文
来两杯美式
返回

Python 知识系列(五):模块和包

By 来两杯美式
发布于

1. 核心概念解析 (Core Concepts)

1.1 模块 (Module)

在Python中,任何一个以 .py 结尾的文件都被认为是一个模块。模块是组织Python代码的基本单位,它可以包含可执行代码、函数、类和变量。模块化使得代码更易于维护、重用和理解。

示例: 创建一个名为 string_utils.py 的模块。

# file: string_utils.py

def reverse_string(s: str) -> str:
    """返回一个字符串的反转形式。"""
    return s[::-1]

def is_palindrome(s: str) -> bool:
    """检查一个字符串是否是回文。"""
    return s == s[::-1]

这个 string_utils.py 文件就是一个模块,模块名就是文件名 string_utils

1.2 包 (Package)

当项目规模变大时,仅使用模块来组织代码会变得混乱。这时就需要使用包。包是一个包含多个模块的目录,它允许我们使用点分命名法(package.module)来组织模块的命名空间。

一个目录要成为一个Python包,必须包含一个特殊的(可以为空的)文件:__init__.py

备注: 在 Python 3.3+ 中,__init__.py 文件不再是强制性的(这被称为“命名空间包”),但为了兼容性和明确性,强烈建议在所有包中都创建它。

示例: 创建一个名为 my_app 的包。

my_app/
├── __init__.py          # 标记 my_app 是一个包
├── string_utils.py      # 模块
└── number_utils.py      # 另一个模块

1.3 import 语句:模块的“消费者”

import 语句用于将一个模块或包中的代码引入到当前模块的命名空间中。

主要形式:

  1. import module_name
    • 导入整个模块。使用时需要通过 module_name.function_name 来调用。
    • 这是最推荐的方式,因为它清晰地表明了函数或变量的来源,避免了命名冲突。
# file: main.py
import string_utils

print(string_utils.reverse_string("hello"))  # 输出: olleh
  1. from module_name import name1, name2
    • 从模块中导入指定的函数、类或变量到当前命名空间。
    • 可以直接使用 name1,无需模块名前缀。
# file: main.py
from string_utils import reverse_string, is_palindrome

print(reverse_string("python"))   # 输出: nohtyp
print(is_palindrome("madam"))   # 输出: True
  1. import module_name as alias
    • 为导入的模块创建别名,特别适用于模块名较长或容易混淆的情况。
    • AI领域中广泛使用此方法,如 import numpy as np
# file: main.py
import string_utils as su

print(su.reverse_string("world")) # 输出: dlrow
  1. from module_name import name as alias
    • 为从模块中导入的特定名称创建别名。
# file: main.py
from string_utils import reverse_string as rev_str

print(rev_str("alias")) # 输出: saila
  1. from module_name import * (不推荐)
    • 导入模块中所有不以下划线 _ 开头的名称。
    • 强烈不推荐在生产代码中使用,因为它会污染当前命名空间,可能导致意外的命名冲突,并降低代码的可读性。

1.4 if __name__ == '__main__': 的妙用

这是一个Python模块中非常重要的结构。它允许一个 .py 文件既可以作为模块被其他代码导入,也可以作为独立脚本直接运行。

示例: 改进 string_utils.py

# file: string_utils.py

def reverse_string(s: str) -> str:
    """返回一个字符串的反转形式。"""
    return s[::-1]

def is_palindrome(s: str) -> bool:
    """检查一个字符串是否是回文。"""
    return s == s[::-1]

# 当这个文件被直接运行时,执行以下代码块
if __name__ == '__main__':
    print("Running self-tests for string_utils...")
    test_str = "level"
    print(f"'{test_str}' reversed is '{reverse_string(test_str)}'")
    print(f"Is '{test_str}' a palindrome? {is_palindrome(test_str)}")

    test_str_2 = "python"
    print(f"Is '{test_str_2}' a palindrome? {is_palindrome(test_str_2)}")

现在,如果你直接运行 python string_utils.py,它会执行测试代码。但如果你在其他文件中 import string_utils,这部分测试代码则不会被执行。

2. 包的最佳实践 (Best Practices)

作为AI应用架构师,编写工程化的、可维护的代码至关重要,下面详细介绍一下不同场景下包的最佳实践案例。

2.1 标准Web应用与微服务

这是最常见的情况,适用于大多数RESTful API服务、内容管理系统或中小型微服务。其核心思想是将所有与应用直接相关的代码都放在一个主包 app内,实现高度内聚,所有的测试代码放在tests内。

适用场景:

推荐结构:

your_project/
├── app/                    # 核心应用代码,高度内聚
│   ├── __init__.py
│   ├── main.py             # 应用入口 (FastAPI/Flask实例)
│   ├── api/                # API路由层 (Endpoints/Views)
│   │   ├── __init__.py
│   │   └── v1/
│   │       ├── __init__.py
│   │       ├── users.py
│   │       └── items.py
│   ├── services/           # 业务逻辑层 (Business Logic)
│   │   ├── __init__.py
│   │   ├── user_service.py
│   │   └── item_service.py
│   ├── models/             # 数据模型 (如 SQLAlchemy ORM)
│   │   ├── __init__.py
│   │   ├── user.py
│   │   └── item.py
│   ├── schemas/            # 数据校验与序列化模型 (如 Pydantic)
│   │   ├── __init__.py
│   │   ├── user_schema.py
│   │   └── item_schema.py
│   ├── core/               # 核心配置、安全、全局依赖
│   │   ├── __init__.py
│   │   └── config.py
│   └── db/                 # 数据库连接与会话管理
│       ├── __init__.py
│       └── session.py
├── tests/                  # 测试代码
└── requirements.txt        # 项目依赖

2.2 复杂业务系统(分层解耦模型)

当项目规模扩大,或者需要严格区分“做什么”(业务逻辑)和“怎么做”(技术实现)时,推荐使用分层解耦模型。这种结构借鉴了“洋葱架构”或“六边形架构”的思想,将核心业务逻辑保护在内层,外部实现(如HTTP、数据库)则作为外层插件。

适用场景:

推荐结构:

your_project/
├── app/                    # 应用入口层 (Delivery/Presentation Layer)
│   ├── __init__.py
│   └── http/               # HTTP API的具体实现
│       └── v1/
│           ├── __init__.py
│           └── users.py
├── config/                 # 独立的配置模块
│   ├── __init__.py
│   └── settings.py
├── internal/               # 内部核心业务领域 (Domain/Business Logic)
│   ├── __init__.py
│   ├── user/               # 按业务领域组织模块
│   │   ├── __init__.py
│   │   ├── user_service.py # 业务逻辑
│   │   ├── user_model.py   # 领域模型
│   │   └── user_repo.py    # 仓储接口 (Interface)
│   └── item/
│       └── ...
├── infrastructure/         # 基础设施层 (Infrastructure Layer)
│   ├── __init__.py
│   ├── db/                 # 数据库实现
│   │   ├── __init__.py
│   │   ├── user_repository_impl.py # 仓储接口的具体实现
│   │   └── session.py
│   └── cache/              # 缓存实现
├── tests/
└── requirements.txt

2.3 AI/LLM驱动的后端服务(混合模型)

这种结构非常适合包含大量非业务性核心组件(如LLM调用、向量数据库、模型管理等)的AI应用。它将“业务逻辑”和“AI核心能力”进行了有效隔离。

适用场景:

优点:

推荐结构:

your_project/
├── app/                    # 应用入口集合 (如HTTP, WebSocket)
│   ├── __init__.py
│   └── http/
│       └── handlers/
│           └── chat.py
├── config/                 # 应用配置文件
│   ├── __init__.py
│   └── settings.py
├── internal/               # 业务应用逻辑 (Application Logic)
│   ├── __init__.py
│   ├── chat_service.py     # 具体的聊天业务流程
│   └── user_session.py
├── core/                   # AI核心能力与基础设施 (Core AI/Infra)
│   ├── __init__.py
│   ├── llm/                # 大语言模型封装
│   ├── vector_store/       # 向量数据库接口
│   ├── prompt/             # Prompt模板管理
│   └── tool/               # Agent工具集
├── shared/                 # 项目内共享的通用代码
│   ├── __init__.py
│   └── exception/          # 自定义异常
└── tests/

3. 导包最佳实践

3.1 命名规范 (PEP 8)

3.2 导入管理 (Import Management)

1. 坚持使用绝对导入 (Absolute Imports)

绝对导入从项目的根目录(src目录)开始指定完整的路径。

# 在 src/my_app/api/endpoints.py 中

# 好 (Absolute Import)
from my_app.utils import string_helper
from my_app.core.models import User

# 不好 (Relative Import, 容易混淆)
# from ..utils import string_helper

为什么推荐绝对导入?

什么时候可以使用相对导入 (Relative Imports)? 在同一个包内部,当模块间关系非常紧密时,可以使用相对导入。例如,core 包内的模块互相引用。

# 在 src/my_app/core/logic.py 中

# from . import models  # 从同级目录的 __init__.py 导入
# from .models import User # 从同级目录的 models.py 导入

一个点 . 代表当前目录。 两个点 .. 代表上级目录。

2. 导入顺序

根据PEP 8,导入应该分组,并按以下顺序排列,每组之间空一行:

  1. 标准库导入 (e.g., os, sys)
  2. 第三方库导入 (e.g., numpy, pandas, langchain)
  3. 本地应用/库特定导入 (e.g., from my_app.core import models)
# file: my_app/api/endpoints.py

# 1. 标准库
import json
import os

# 2. 第三方库
from fastapi import FastAPI
import numpy as np

# 3. 本地应用
from my_app.core import models
from my_app.utils import string_helper

使用 isortruff 等工具可以自动格式化导入顺序。

3.3 利用 __init__.py 打造简洁的包API

__init__.py 不仅可以标记一个目录为包,还可以用来定义包的公共API,使得包的调用者更加方便。

示例:

假设 my_app/utils/string_helper.py 中有一个函数 clean_text()

如果不做任何处理,外部调用需要这样:

from my_app.utils.string_helper import clean_text

我们可以在 my_app/utils/__init__.py 中“提升”这个函数:

# file: src/my_app/utils/__init__.py

from .string_helper import clean_text

# 可选: 使用 __all__ 定义 `from my_app.utils import *` 时导出的内容
__all__ = ['clean_text']

现在,外部调用可以简化为:

from my_app.utils import clean_text

clean_text(" some text ")

这为包的使用者提供了一个更简洁、更稳定的接口,即使内部文件结构发生变化,只要 __init__.py 不变,外部调用代码就无需修改。

3.4 避免循环导入 (Circular Imports)

这是Python初学者最常遇到的问题之一。当两个模块互相导入对方时,就会发生循环导入。

场景:

# file: module_a.py
from module_b import B

class A:
    def __init__(self):
        self.b_instance = B()

def helper_function():
    print("Helper from A")
# file: module_b.py
from module_a import helper_function # <-- 循环导入

class B:
    def do_something(self):
        helper_function()

运行 python module_a.py 会导致 ImportError

解决方案:

  1. 重构设计: 将共享的依赖项移动到一个新的、更低层次的模块中(例如 shared.py)。
  2. 延迟导入: 在函数或方法内部进行导入,而不是在模块的顶层。这会推迟导入操作,直到函数被实际调用。
# file: module_b.py (修正后)

class B:
    def do_something(self):
        # 在需要时才导入
        from module_a import helper_function
        helper_function()

虽然这种方法能解决问题,但首选方案仍然是优化代码设计。


分享这篇文章:
通过邮件分享这篇文章✓ 链接已复制
所属专题
Python
第 5 / 8 篇
查看系列全部文章
  1. 01.Python 知识系列(一):极简核心语法
  2. 02.Python 知识系列(二):面向对象编程
  3. 03.Python 知识系列(三):从 0 开始理解函数
  4. 04.Python 知识系列(四):装饰器详解
  5. 05.Python 知识系列(五):模块和包
  6. 06.Python 知识系列(六):异步编程
  7. 07.Python 知识系列(七):虚拟环境与依赖管理
  8. 08.Python 知识系列(八):企业级开发场景最佳实践

上一篇
Python 知识系列(六):异步编程
下一篇
Python 知识系列(四):装饰器详解