模块和包#
模块和包是 Python 中组织代码的方式。模块是包含 Python 代码的文件,包是包含多个模块的目录。
模块(Module)#
什么是模块?#
模块是一个包含 Python 代码的文件,通常以 .py 为扩展名。
# 文件:math_utils.py
def add(a, b):
return a + b
def multiply(a, b):
return a * b
PI = 3.14159导入模块#
# 导入整个模块
import math_utils
result = math_utils.add(2, 3)
# 导入并重命名
import math_utils as mu
result = mu.add(2, 3)
# 从模块导入特定函数
from math_utils import add, multiply
result = add(2, 3)
# 导入所有(不推荐)
from math_utils import *
result = add(2, 3)模块搜索路径#
Python 按以下顺序搜索模块:
- 当前目录
- PYTHONPATH 环境变量指定的目录
- Python 标准库目录
- site-packages 目录
import sys
print(sys.path) # 查看模块搜索路径包(Package)#
什么是包?#
包是一个包含 __init__.py 文件的目录,可以包含多个模块。
mypackage/
__init__.py
module1.py
module2.py
subpackage/
__init__.py
module3.py__init__.py 文件#
__init__.py 文件使目录成为包,在包被导入时自动执行。
基本作用#
- 标识包:使目录被 Python 识别为包
- 初始化代码:包被导入时执行其中的代码
- 控制导入:定义包的公共接口,简化导入路径
Python 版本差异#
- Python 3.3 之前:
__init__.py必须存在(可以是空文件) - Python 3.3+:支持命名空间包,
__init__.py可选
空 __init__.py 文件#
如果 __init__.py 为空文件(或只有注释),包仍然可以正常工作:
# mypackage/__init__.py
# (空文件,或只有注释)
# 包结构:
# mypackage/
# __init__.py (空文件)
# module1.py (包含 function1, function2)
# module2.py (包含 function3)空文件时的导入行为:如果 __init__.py 为空,import mypackage 只会导入包本身,不会自动导入包内的模块和内容。
# ✅ 可以:导入包本身
import mypackage
# ✅ 可以:通过完整路径导入
from mypackage.module1 import function1, function2
from mypackage.module2 import function3
# ❌ 不可以:简化路径导入(因为 __init__.py 中没有导入)
from mypackage import function1 # AttributeError: module 'mypackage' has no attribute 'function1'
# ⚠️ 注意:from package import * 的行为
from mypackage import * # 不会导入任何内容非空 __init__.py 文件#
非空 __init__.py 可以包含多种内容:
# mypackage/__init__.py
# 1. 包初始化代码
print("正在初始化 mypackage...")
# 2. 定义包的公共接口(简化导入路径)
from .module1 import function1, Class1
from .module2 import function2
# 3. 控制 from package import * 的行为
__all__ = ['function1', 'function2', 'Class1']
# 4. 包级别变量
__version__ = "1.0.0"
AUTHOR = "Your Name"
# 5. 包级别函数
def package_helper():
"""包级别的辅助函数"""
return "Helper function"非空文件时的导入行为:
# ✅ 可以:通过完整路径导入(始终可用)
from mypackage.module1 import function1, function2
from mypackage.module2 import function3
# ✅ 可以:简化路径导入(因为 __init__.py 中已导入)
from mypackage import function1 # 可以直接导入
from mypackage import Class1 # 可以直接导入
# ❌ 不可以:简化路径导入未在 __init__.py 中的对象
from mypackage import function2 # ❌ 错误:function2 不在 __init__.py 中
# ✅ 可以:from package import *(导入 __all__ 中定义的内容)
from mypackage import * # 导入 function1, function2, Class1导入机制详解#
核心原则:__init__.py 中的导入只是简化导入路径,不影响通过完整路径的导入。
# 假设包结构:
# mypackage/
# __init__.py (只导入了 function1)
# module1.py (包含 function1, function2)
# module2.py (包含 function3)
# __init__.py 内容:
from .module1 import function1
# 注意:没有导入 function2 和 function3
# 外部文件的导入方式:
# 方式1:完整路径导入(始终可用,无论 __init__.py 是否为空)
from mypackage.module1 import function2 # ✅ 可以导入
from mypackage.module2 import function3 # ✅ 可以导入
# 方式2:简化路径导入(仅在 __init__.py 中已导入时可用)
from mypackage import function1 # ✅ 可以导入(因为 __init__.py 中已导入)
from mypackage import function2 # ❌ 错误:function2 不在 __init__.py 中执行时机#
# 当执行 import mypackage 时:
# 1. Python 查找 mypackage 目录
# 2. 执行 mypackage/__init__.py 中的代码
# 3. 创建 mypackage 命名空间对象
import mypackage # __init__.py 中的代码会执行,结果归于mypackage命名空间标准库模块#
Python 提供了丰富的标准库模块。
os 模块#
import os
# 获取当前工作目录
current_dir = os.getcwd()
# 改变工作目录
os.chdir("/path/to/directory")
# 列出目录内容
files = os.listdir(".")
# 创建目录
os.makedirs("new_directory", exist_ok=True)
# 删除文件
os.remove("file.txt")
# 环境变量
home = os.environ.get("HOME")sys 模块#
import sys
# 命令行参数
args = sys.argv
# 退出程序
sys.exit(0)
# Python 版本
print(sys.version)
# 模块搜索路径
print(sys.path)
# 标准输入输出
sys.stdout.write("Hello\n")
user_input = sys.stdin.readline()datetime 模块#
from datetime import datetime, date, timedelta
# 当前时间
now = datetime.now()
print(now)
# 创建日期时间
dt = datetime(2024, 1, 1, 12, 30, 0)
# 格式化日期时间
formatted = now.strftime("%Y-%m-%d %H:%M:%S")
print(formatted)
# 解析字符串
dt = datetime.strptime("2024-01-01", "%Y-%m-%d")
# 日期运算
today = date.today()
tomorrow = today + timedelta(days=1)json 模块#
import json
# 将Python对象转换为JSON字符串
data = {"name": "Alice", "age": 30}
json_str = json.dumps(data)
print(json_str) # '{"name": "Alice", "age": 30}'
# 将JSON字符串转换为Python对象
data = json.loads('{"name": "Alice", "age": 30}')
print(data["name"]) # Alice
# 读写JSON文件
with open("data.json", "w") as f:
json.dump(data, f)
with open("data.json", "r") as f:
data = json.load(f)random 模块#
import random
# 随机整数
num = random.randint(1, 10)
# 随机浮点数
num = random.random() # 0.0 到 1.0
# 随机选择
choice = random.choice([1, 2, 3, 4, 5])
# 随机打乱列表
numbers = [1, 2, 3, 4, 5]
random.shuffle(numbers)
# 随机采样
sample = random.sample([1, 2, 3, 4, 5], 3)re 模块(正则表达式)#
import re
# 匹配
pattern = r"\d+"
text = "我有3个苹果和5个橙子"
matches = re.findall(pattern, text)
print(matches) # ['3', '5']
# 搜索
match = re.search(r"\d+", text)
if match:
print(match.group()) # 3
# 替换
new_text = re.sub(r"\d+", "X", text)
print(new_text) # 我有X个苹果和X个橙子
# 编译正则表达式(提高性能)
pattern = re.compile(r"\d+")
matches = pattern.findall(text)collections 模块#
from collections import Counter, defaultdict, deque
# Counter - 计数器
counter = Counter([1, 2, 2, 3, 3, 3])
print(counter) # Counter({3: 3, 2: 2, 1: 1})
# defaultdict - 默认字典
dd = defaultdict(list)
dd["key"].append("value") # 自动创建空列表
# deque - 双端队列
dq = deque([1, 2, 3])
dq.appendleft(0) # 左侧添加
dq.append(4) # 右侧添加itertools 模块#
from itertools import combinations, permutations, product
# 组合
combs = list(combinations([1, 2, 3], 2))
print(combs) # [(1, 2), (1, 3), (2, 3)]
# 排列
perms = list(permutations([1, 2, 3], 2))
print(perms) # [(1, 2), (1, 3), (2, 1), (2, 3), (3, 1), (3, 2)]
# 笛卡尔积
prods = list(product([1, 2], [3, 4]))
print(prods) # [(1, 3), (1, 4), (2, 3), (2, 4)]模块的特殊属性#
# __name__ - 模块名称
# 当模块被直接运行时,__name__ 为 "__main__"
# 当模块被导入时,__name__ 为模块名
# 文件:my_module.py
def hello():
print("Hello from module")
if __name__ == "__main__":
# 只有直接运行此文件时才会执行
hello()
# __file__ - 模块文件路径
import my_module
print(my_module.__file__)
# __doc__ - 模块文档字符串
print(my_module.__doc__)命名空间#
模块命名空间#
每个模块有独立的命名空间(名字 → 对象的映射)。导入模块时:
import m:将模块对象m加入当前命名空间,通过m.xxx访问模块内名字。from m import a:将m中的a绑定到当前命名空间,直接写a即可使用。
函数在定义时绑定到其所在模块的命名空间。执行时先在该模块命名空间中查找名字,再按 LEGB 查找全局、内置等。
导入对当前命名空间的影响#
| 写法 | 当前命名空间新增 |
|---|---|
import m | 名字 m(模块对象) |
import m as M | 名字 M |
from m import a, b | 名字 a, b(不引入 m) |
from m import * | m.__all__ 中的名字(若无 __all__ 则为除 _ 开头的全部) |
命名空间包(Namespace Package)#
命名空间包是 Python 3.3+ 的一种包形式:多个目录共同提供一个包名,且这些目录可以没有 __init__.py。
- 用途:把同一逻辑包分散在不同路径(如
site-packages下的多个位置),或做插件式扩展。 - 与普通包区别:无
__init__.py,各目录各自提供一部分子模块/子包,导入时多个目录会合并成同一个包命名空间。
# 若 path1/pkg 与 path2/pkg 均无 __init__.py,且都在 sys.path 中:
# import pkg 会得到命名空间包,可同时使用 pkg 下来自 path1 和 path2 的内容相对导入和绝对导入#
导入一个函数时,该函数内部仍使用自己所在模块里的其他名字,无需在调用方重复导入。执行时先在模块命名空间查找,再查全局、内置等。
绝对导入#
# 从包根目录开始的完整路径
from mypackage.module1 import function1
from mypackage.subpackage.module3 import function3相对导入#
# 在包内部使用相对导入
# 文件:mypackage/module1.py
from .module2 import function2 # 同一包
from ..subpackage.module3 import function3 # 上级包的子包第三方包管理#
pip 安装#
# 安装包
pip install package_name
# 安装特定版本
pip install package_name==1.0.0
# 从requirements.txt安装
pip install -r requirements.txt
# 卸载包
pip uninstall package_name
# 列出已安装的包
pip list
# 查看包信息
pip show package_namerequirements.txt#
# requirements.txt
requests==2.28.0
numpy>=1.20.0
pandas<2.0.0虚拟环境#
使用虚拟环境可以隔离不同项目的依赖。
# 创建虚拟环境
python -m venv myenv
# 激活虚拟环境
# Windows
myenv\Scripts\activate
# macOS/Linux
source myenv/bin/activate
# 停用虚拟环境
deactivate模块导入最佳实践#
- 导入顺序:标准库 → 第三方库 → 本地模块
- 使用绝对导入(除非在包内部)
- 避免
from module import * - 使用
__all__定义公共接口 - 合理使用
if __name__ == "__main__"
# 推荐的导入顺序
import os
import sys
from datetime import datetime
import requests
import numpy as np
from mypackage import module1
from mypackage.module2 import function2下一步#
掌握了模块和包后,可以学习: