Python(十一):第十章: 模块与包

第十章: 模块与包

模块是 Python 中组织代码的基本单位,本质上是一个包含 Python 定义和语句的文件。本文将深入探讨模块与包的概念、使用方法以及高级应用技巧,结合 PyCharm 中的包管理最佳实践。

10.1 模块分类

在 Python 生态系统中,模块可以分为三大类:

模块类型说明示例
内置模块Python 解释器自带的标准库模块os, sys, math, datetime
第三方模块社区开发者创建的模块numpy, pandas, requests
自定义模块开发者自己创建的模块项目中自定义的.py 文件

重要提示:首次导入自定义模块时,Python 会执行该模块中的所有顶层代码。每个模块都有自己的名称空间,模块中定义的变量属于该模块的名称空间。

10.2 模块导入方式

10.2.1 导入整个模块

1
2
3
4
5
import module_name

## 使用模块中的内容
module_name.function_name()
module_name.variable_name

10.2.2 从模块导入特定内容

1
2
3
4
5
from module_name import function_name, variable_name

## 直接使用,无需模块名前缀
function_name()
print(variable_name)

工作原理:使用 from 方式导入时,被导入的对象会直接引用模块中对应变量的内存地址,可以直接使用而无需模块前缀。

10.2.3 导入时重命名

1
2
3
4
5
6
7
## 模块重命名
import module_name as alias
alias.function_name()

## 函数重命名
from module_name import function_name as fn
fn()

10.2.4 导入所有内容(不推荐)

1
from module_name import *

注意:这种方式可能导致命名冲突,不利于代码可读性和维护性。在大型项目中应避免使用。

10.3 控制模块导入

我们可以在每一个模块的 __init__ 文件中使用如下的操作

可以使用 __all__ 列表来控制 from module import * 语句导入的内容:

1
2
3
4
5
## 在模块文件中定义
__all__ = ['function1', 'function2', 'CONSTANT1']

## 不在__all__中的变量和函数,使用from module import *时不会被导入
_private_variable = "这个变量不会被导入"

10.4 模块的特殊属性

10.4.1 __name__ 属性

每个 Python 文件都有一个 __name__ 属性:

  • 当直接运行该文件时,__name__ 的值为 '__main__'
  • 当作为模块被导入时,__name__ 的值为模块名

这个特性可用于编写既可作为模块导入,又可独立运行的代码:

1
2
3
4
5
6
7
8
9
10
11
12
## 模块内的代码
def main_function():
print("执行主函数逻辑")

def helper_function():
print("辅助函数")

if __name__ == '__main__':
# 这部分代码只在直接运行文件时执行
# 作为模块导入时不会执行
main_function()
print("运行模块自测试...")

10.4.2 From 模块无法识别问题

Python 在导入模块时会按照一定顺序搜索模块文件,在有些情况下我们自己定义的模块不一定会被检测到如下列图片:

image-20250426173806981

例如,当我们的模型层期望用到另外一个 的代码时,往往会这样引入:

1
2
from ecommerce_system.ecommerce.interfaces.payment import PaymentProcessor
from ecommerce_system.ecommerce.interfaces.shipping import ShippingMethod, Address

但这样是无法被识别到的,我们应该是需要这样做:

  • 1.标记外层的包为根包
  • 2.去掉 ecommerce_system 前缀

这样 Pycharm 就会检测到我们是在这个包下进行操作的,即可识别到

从我们的根包出发,也就是图片中蓝色的包(这个是需要在 IDEA)手动标注的

image-20250426174310742

10.5 包的概念与使用

包是一种特殊的模块,它是一个包含 __init__.py 文件的目录,用于组织相关模块。包可以包含子包和模块,形成层次结构。

10.5.1 包的结构示例

1
2
3
4
5
6
7
mypackage/
__init__.py # 使目录成为包的文件
module1.py # 模块1
module2.py # 模块2
subpackage/ # 子包
__init__.py
module3.py

10.5.2 __init__.py 文件的作用

  1. 标识目录为包:Python 将包含 __init__.py 的目录视为包
  2. 初始化包:在导入包时执行初始化代码
  3. 定义包的公共接口:通过 __all__ 列表指定 from package import * 时导入的内容
  4. 自动导入子模块:可以在 __init__.py 中导入子模块,使它们在导入包时可用

示例 __init__.py

1
2
3
4
5
6
7
8
9
10
11
## mypackage/__init__.py

## 从子模块导入主要函数,使它们在导入包时可用
from .module1 import function1
from .module2 import function2

## 定义包导出的符号
__all__ = ['function1', 'function2']

## 包初始化代码
print("mypackage 已加载")

10.5.3 包的导入方式

10.5.3.1 导入包中的模块
1
2
3
4
5
6
7
8
9
10
11
## 完整路径导入
import mypackage.module1
mypackage.module1.function1()

## 导入特定模块
from mypackage import module1
module1.function1()

## 导入子包中的模块
from mypackage.subpackage import module3
module3.function3()
10.5.3.2 导入包中特定内容
1
2
from mypackage.module1 import function1
function1()

10.5.4 相对导入与绝对导入

10.5.4.1 绝对导入

从项目的顶级包开始导入:

1
from package_name.module_name import function_name
10.5.4.2 相对导入

使用点号表示相对位置:

  • .module_name:当前包中的模块
  • ..module_name:父包中的模块
  • ...module_name:祖父包中的模块
1
2
3
4
5
6
7
8
## 在mypackage.subpackage.module3中导入同级模块
from . import another_module # 导入同级模块

## 导入父包中的模块
from .. import module1 # 导入父包中的模块

## 导入父包中模块的特定函数
from ..module2 import function2

注意:相对导入只能在包内使用,不能在顶级模块中使用。相对导入基于当前模块的 __name__ 属性,而直接运行的脚本的 __name__ 总是 '__main__'

10.6 高级应用技巧

10.6.1 动态导入

在运行时根据条件动态导入模块:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
## 方法1:使用__import__
def import_module_by_name(module_name):
return __import__(module_name)

## 方法2:使用importlib(推荐)
import importlib

def get_module(module_name):
"""根据名称动态导入模块"""
try:
return importlib.import_module(module_name)
except ImportError as e:
print(f"无法导入模块 {module_name}: {e}")
return None

## 示例:根据条件选择不同的模块
def get_database_module(db_type):
"""根据数据库类型动态选择数据库模块"""
if db_type.lower() == 'mysql':
return importlib.import_module('mysql.connector')
elif db_type.lower() == 'postgresql':
return importlib.import_module('psycopg2')
else:
return importlib.import_module('sqlite3')

10.6.2 延迟导入

推迟导入耗时模块,直到真正需要时才导入,可以加快程序启动速度:

1
2
3
4
5
6
7
8
9
10
11
12
13
def function_that_needs_numpy():
"""只在函数被调用时导入numpy"""
import numpy as np
return np.array([1, 2, 3])

def process_image(image_path):
"""图像处理函数,仅在需要时导入PIL"""
# 只在需要处理图像时才导入PIL
from PIL import Image

img = Image.open(image_path)
# 处理图像...
return img

10.6.3 使用 __slots__ 优化内存

在模块级别的类中使用 __slots__ 限制属性,提高内存效率:

1
2
3
4
5
6
7
8
9
10
11
12
class DataPoint:
"""使用__slots__优化内存的数据点类"""
__slots__ = ['x', 'y', 'z'] # 只允许这些属性

def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z

def distance_from_origin(self):
"""计算到原点的距离"""
return (self.x**2 + self.y**2 + self.z**2) ** 0.5

10.7 包的发布与安装

创建自己的包并发布到 PyPI:

10.7.1 创建 setup.py 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from setuptools import setup, find_packages

setup(
name="mypackage",
version="0.1.0",
author="Your Name",
author_email="your.email@example.com",
description="A short description of the package",
long_description=open("README.md").read(),
long_description_content_type="text/markdown",
url="https://github.com/yourusername/mypackage",
packages=find_packages(),
classifiers=[
"Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
],
python_requires='>=3.6',
install_requires=[
'dependency1>=1.0.0',
'dependency2>=2.0.0',
],
)

10.7.2 打包与上传

1
2
3
4
5
6
7
8
## 安装打包工具
pip install --upgrade setuptools wheel twine

## 构建分发包
python setup.py sdist bdist_wheel

## 上传到PyPI
twine upload dist/*

10.7.3 安装包

1
pip install mypackage

10.8 PyCharm 中的包管理

PyCharm 提供了强大的图形界面来管理 Python 包,让包的安装和管理变得简单高效。

10.8.1 使用 Python Packages 工具

在 PyCharm 中管理包的最简单方法是使用内置的 Python Packages 工具:

PyCharm Python Packages 界面

  1. 点击底部的 Python Packages 标签打开包管理器
  2. 在搜索框中输入要安装的包名称
  3. 点击包右侧的 Install 按钮安装包
  4. 已安装的包会显示在 Installed 标签下,可以查看版本并进行升级或卸载操作

10.8.2 更改 PyCharm 的 pip 源

默认的 PyPI 源在国内访问可能较慢,可以更换为国内镜像以提高下载速度:

PyCharm 更改 pip 源

  1. 在 Python Packages 界面点击左上角的齿轮图标
  2. 点击 “+” 按钮添加新的软件源
  3. 输入国内镜像源地址,例如:

10.8.3 使用 Project Interpreter 管理包

除了 Python Packages 工具外,还可以通过 Project Interpreter 设置管理包:

PyCharm Project Interpreter

  1. 进入 File > Settings > Project > Python Interpreter
  2. 点击 “+” 按钮添加新包
  3. 在弹出窗口中搜索并安装需要的包

10.8.4 导出和导入项目依赖

在团队开发中,共享项目依赖非常重要。PyCharm 提供了方便的方式来管理 requirements.txt 文件:

10.8.4.1 导出依赖

推荐使用 pipreqs 工具导出仅项目使用的依赖包:

1
2
3
4
5
## 安装pipreqs
pip install pipreqs

## 在项目根目录执行
pipreqs . --encoding=utf8 --force

提示--force 参数会强制覆盖已存在的 requirements.txt 文件,--encoding=utf8 确保使用 UTF-8 编码处理文件。

10.8.4.2 导入依赖

在 PyCharm 的 Terminal 中执行:

1
pip install -r requirements.txt

或者指定国内镜像源:

1
pip install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple/

10.8.5 使用虚拟环境

PyCharm 支持多种虚拟环境管理工具,如 Virtualenv、Pipenv 和 Conda:

PyCharm 虚拟环境设置

  1. 创建新项目时选择虚拟环境类型
  2. 对于现有项目,可以在 File > Settings > Project > Python Interpreter 中配置
  3. 点击齿轮图标,选择 “Add…”,然后选择合适的虚拟环境类型
10.8.5.1 虚拟环境工具对比
工具优点缺点适用场景
Virtualenv轻量级,易于使用需要手动维护 requirements.txt简单项目
Pipenv自动管理依赖,有锁文件比 Virtualenv 慢中型团队项目
Conda同时管理 Python 版本和包占用空间大数据科学项目

10.9 模块开发最佳实践

10.9.1 模块组织

  • 相关功能放在同一个模块中
  • 单个模块不要过大,保持在 1000 行以内
  • 使用子模块和子包组织复杂功能
  • 使用清晰的命名约定,避免与标准库和流行第三方库冲突

10.9.2 导入顺序

遵循 PEP8 建议的导入顺序:

1
2
3
4
5
6
7
8
9
10
11
## 1. 标准库导入
import os
import sys

## 2. 相关第三方库导入
import numpy as np
import pandas as pd

## 3. 本地应用/库特定导入
from mypackage import module1
from .utils import helper_function

10.9.3 文档化模块和包

为模块、类和函数编写清晰的文档字符串:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
"""
模块名称: data_processing

这个模块提供了处理数据的实用函数。

主要功能:
* 数据清洗
* 特征工程
* 数据转换
"""

def clean_data(df):
"""
清洗输入的DataFrame。

参数:
df (pandas.DataFrame): 需要清洗的数据帧

返回:
pandas.DataFrame: 清洗后的数据帧

示例:
>>> import pandas as pd
>>> df = pd.DataFrame({'A': [1, None, 3], 'B': [4, 5, None]})
>>> clean_data(df)
A B
0 1.0 4.0
2 3.0 5.0
"""
# 函数实现...