模块和包#

模块和包是 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 按以下顺序搜索模块:

  1. 当前目录
  2. PYTHONPATH 环境变量指定的目录
  3. Python 标准库目录
  4. 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 文件使目录成为包,在包被导入时自动执行。

基本作用#

  1. 标识包:使目录被 Python 识别为包
  2. 初始化代码:包被导入时执行其中的代码
  3. 控制导入:定义包的公共接口,简化导入路径

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_name

requirements.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

模块导入最佳实践#

  1. 导入顺序:标准库 → 第三方库 → 本地模块
  2. 使用绝对导入(除非在包内部)
  3. 避免 from module import *
  4. 使用 __all__ 定义公共接口
  5. 合理使用 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

下一步#

掌握了模块和包后,可以学习:

  1. 文件操作 - 学习文件读写操作
  2. 高级特性 - 学习生成器、迭代器等高级特性