Logo
Published on

3.9.模块和包

Authors
  • avatar
    Name
    xiaobai
    Twitter

1.概述

模块和包是 Python 中组织代码的重要方式,它们让代码更加结构化、可维护和可重用。掌握模块和包的使用是 Python 编程的基础技能。

2.核心概念

  • 模块:包含 Python 定义和语句的 .py 文件
  • :包含多个模块的目录,必须有 __init__.py 文件
  • 命名空间:每个模块和包都有独立的命名空间
  • 导入:将模块或包引入当前程序的过程

3.模块(Module)

3.1.什么是模块?

模块是一个包含 Python 定义和语句的 .py 文件,可以包含:

  • 函数定义
  • 类定义
  • 变量
  • 可执行代码

3.2.模块的优势

  • 代码复用:公共函数/类可以集中管理
  • 命名空间隔离:避免命名冲突
  • 可维护性:分文件组织更易管理大型项目
  • 模块化设计:提高代码的模块化程度

3.3.创建和使用模块

3.3.1.创建模块

# mymodule.py
def hello():
    print("Hello from mymodule!")

def add(a, b):
    return a + b

class Calculator:
    def multiply(self, a, b):
        return a * b

3.3.2.导入模块

# main.py
import mymodule

# 使用模块中的函数和类
mymodule.hello()
result = mymodule.add(3, 4)
calc = mymodule.Calculator()
print(calc.multiply(2, 3))

3.3.3.导入特定成员

# 导入特定函数和类
from mymodule import hello, add, Calculator

# 直接使用,无需模块名前缀
hello()
result = add(3, 4)
calc = Calculator()

3.4.模块查找路径

Python 按照以下顺序查找模块:

  1. 当前程序所在目录
  2. PYTHONPATH 环境变量指定的目录
  3. Python 安装时的标准库目录
import sys

# 查看模块搜索路径
print(sys.path)

3.5.模块命名规范

  • 使用小写字母和下划线
  • 避免与标准库同名
  • 导入时无需写 .py 后缀

4.导入方式详解

4.1.导入整个模块

import math

# 通过模块名访问成员
print(math.sqrt(16))  # 输出 4.0
print(math.pi)        # 输出 3.141592653589793

4.1.1.优点

  • 命名空间清晰:明确成员来自哪个模块
  • 避免命名冲突:同名函数不会冲突
  • 代码可读性:容易理解函数来源

4.1.2.适用场景

  • 使用模块中多个成员时
  • 需要明确函数来源时
  • 避免命名冲突时

4.2.导入所有成员(import *)

from 模块名 import *

4.2.1.示例

# mymodule.py
def foo():
    print("foo")

def bar():
    print("bar")
from mymodule import *
foo()  # 输出 foo
bar()  # 输出 bar

4.2.2.控制导入内容

# mymodule.py
__all__ = ['foo']  # 只允许导入 foo

def foo():
    print("foo")

def bar():
    print("bar")

4.2.3.注意事项

  • 不推荐使用:容易造成命名冲突
  • 仅限模块:不适用于包
  • 命名空间污染:直接暴露在当前命名空间
  • 可读性差:难以确定函数来源

4.2.4.推荐做法

# 推荐:明确导入
from mymodule import foo, bar

# 推荐:导入整个模块
import mymodule

4.3.使用别名

import 模块名 as 新名字
from 模块名 import 成员 as 新名字

4.3.1.示例

import numpy as np
import matplotlib.pyplot as plt
from math import factorial as fact

# 使用别名
arr = np.array([1, 2, 3])
plt.plot([1, 2, 3])
result = fact(5)

4.3.2.别名优势

  • 代码简洁:缩短长模块名
  • 避免冲突:解决命名冲突
  • 社区约定:遵循常用别名习惯

4.3.3.常用别名

  • numpynp
  • pandaspd
  • matplotlib.pyplotplt
  • tensorflowtf

4.4.模块的 name 属性

__name__ 属性用于判断模块是被直接运行还是被导入:

  • 直接运行时:__name__"__main__"
  • 被导入时:__name__ 为模块名称

4.4.1.示例

# mymodule.py
def func():
    print("hello")

if __name__ == "__main__":
    # 只在直接运行时执行
    func()

4.4.2.使用场景

运行方式__name__作用
直接运行"__main__"执行主流程
被导入模块名提供接口

4.4.3.最佳实践

  • 所有模块入口脚本都使用 if __name__ == "__main__":
  • 既可作为工具库导入,也可作为脚本运行
  • 提高代码的灵活性和可测试性

5.包(Package)

5.1.什么是包?

包是组织多个模块的目录,包含:

  • 多个模块(.py 文件)
  • 子包(嵌套目录)
  • __init__.py 文件(必需)

5.2.包的优势

  • 项目结构清晰:分层组织代码
  • 避免命名冲突:独立的命名空间
  • 便于团队开发:模块化协作
  • 组件重用:可复用的代码单元

5.3.包的结构

mypackage/
├── __init__.py      # 包初始化文件(必需)
├── module1.py       # 模块1
├── module2.py       # 模块2
└── subpackage/      # 子包
    ├── __init__.py  # 子包初始化文件
    └── module3.py   # 子包模块

5.4.创建包结构

5.4.1.目录结构

myproject/
├── main.py
└── mypackage/
    ├── __init__.py
    ├── module1.py
    ├── module2.py
    └── subpackage/
        ├── __init__.py
        └── module3.py

5.4.2.初始化文件 init.py

# mypackage/__init__.py
"""mypackage 包的初始化文件"""

__version__ = "1.0.0"
__author__ = "Your Name"

# 导入包中的模块,方便用户使用
from .module1 import function1
from .module2 import Class2

# 定义包级别的变量
package_variable = "这是包变量"

print("mypackage 包被导入")

5.4.3.包中的模块

# mypackage/module1.py
def function1():
    return "这是 module1 的 function1"

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

# mypackage/module2.py
class Class2:
    def __init__(self, value):
        self.value = value
    
    def display(self):
        return f"Class2 的值: {self.value}"

# mypackage/subpackage/module3.py
def function3():
    return "这是子包中的 function3"

5.5.导入包和模块语法

5.5.1.导入整个包

# 导入整个包(执行 __init__.py)
import mypackage

5.5.2.导入包内模块

# 导入包内指定模块
from mypackage import module1

# 导入包中模块的成员
from mypackage.module1 import function1

5.5.3.导入子包

# 导入子包中的模块
from mypackage.subpackage import module3

# 导入子包模块的成员
from mypackage.subpackage.module3 import function3

5.6.相对导入与绝对导入

5.6.1.绝对导入

从项目根包开始写完整路径:

# 从 mypackage 包中的 module1 模块导入 function1 函数
from mypackage.module1 import function1

# 从子包中导入
from mypackage.subpackage.module3 import function3

5.6.2.相对导入

在包内部使用点号语法:

# 同级目录
from . import module1

# 上一级包
from .. import module2

# 同级模块
from .module1 import func

# 上级包的子包
from ..subpackage import module3

5.6.3.注意事项

  • 相对导入只能用于包内部模块
  • 不能在最顶层脚本(main.py)中使用
  • 包内各文件的相对路径以包目录为基准

5.7.init.py 的作用

  • 声明包:标识目录为 Python 包
  • 初始化操作:包级变量、配置、统一导入
  • 控制导入:通过 __all__ 列表控制可见成员

5.8.导入包和模块示例

# main.py

# 导入包中的特定模块
from mypackage import module1
print(module1.function1())

# 导入包中的特定函数/类
from mypackage.module2 import Class2
obj = Class2(42)
print(obj.display())

# 导入子包中的模块
from mypackage.subpackage import module3
print(module3.function3())

# 使用 __init__.py 中导入的内容
from mypackage import function1
print(function1())

# 导入包变量
import mypackage
print(mypackage.package_variable)

5.9.输出结果

mypackage 包被导入
这是 module1 的 function1
Class2 的值: 42
这是子包中的 function3
这是 module1 的 function1
这是包变量

6.高级导入技巧

6.1.相对导入

相对导入在包内部使用点号语法引用同包或父包内容:

6.1.1.基本语法

  • from . import xxx:从当前包导入
  • from .. import xxx:从父包导入
  • from .subpackage import yyy:从子包导入

6.1.2.示例

mypackage/
├── __init__.py
├── module1.py
├── module2.py
└── subpackage/
    ├── __init__.py
    └── module3.py
# module1.py
from . import module2        # 导入同级模块
from .subpackage import module3  # 导入子包模块

def function1():
    return "这是 module1 的 function1"

6.1.3.注意事项

  • 只能在包内部使用
  • 不能在主程序(main.py)中使用
  • 有助于包的重命名与迁移
  • 点数与目录层次相关

6.2.动态导入

动态导入在程序运行时根据需要导入模块,常用于插件系统和可扩展框架。

6.2.1.使用 import()

module_name = "math"
math_module = __import__(module_name)
print(math_module.sqrt(9))   # 输出: 3.0

6.2.2.使用 importlib

import importlib

# 导入标准库模块
module = importlib.import_module("json")
data = module.loads('{"a": 1}')
print(data)   # 输出: {'a': 1}

# 导入包内模块
module_name = "mypackage.module1"
mod = importlib.import_module(module_name)
result = mod.function1()
print(result)  # 输出: 这是 module1 的 function1

6.2.3.动态导入优势

  • 按需加载:减少资源占用
  • 插件机制:支持动态扩展功能
  • 配置驱动:根据配置加载不同组件

6.2.4.注意事项

  • 确保模块名合法且可找到
  • 过度使用影响代码可读性
  • 仅在确有需要时使用

7.模块搜索路径

7.1.搜索路径顺序

Python 按以下顺序查找模块:

  1. 当前脚本所在目录
  2. 标准库路径(如 /usr/lib/python3.x
  3. 第三方库目录(如 site-packages
  4. PYTHONPATH 环境变量指定的路径

7.2.查看和修改搜索路径

import sys

# 查看当前搜索路径
print(sys.path)

# 临时添加自定义路径
sys.path.append('/path/to/your/modules')

7.3.模块查找优先级

  1. 内存缓存:已加载的模块(防止重复导入)
  2. 内建模块:如 sysos
  3. sys.path 目录:按顺序逐一查找

7.4.最佳实践

  • 合理组织项目结构
  • 使用 __init__.py 标识包
  • 规范包和模块命名
  • 避免随意修改 sys.path

8.创建可安装的包

8.1.包目录结构

mypackage/
├── mypackage/
│   ├── __init__.py
│   ├── module1.py
│   └── module2.py
├── tests/
│   └── test_module1.py
├── setup.py
├── README.md
└── LICENSE

8.2.创建步骤

  1. 组织包目录结构
    • 外层目录:项目根目录
    • 内层目录:实际包目录(包含 __init__.py
    • tests/:测试代码目录
  2. 创建 __init__.py
    • 声明目录为 Python 包
    • 可包含包级初始化逻辑
  3. 编辑 setup.py 配置
    • 声明包名称、版本、依赖等元信息
  4. (可选)增加 MANIFEST.in
    • 包含非 Python 文件(数据文件、README 等)
  5. 安装和发布
    • 本地安装:pip install .pip install -e .
    • 发布到 PyPI:python setup.py sdist bdist_wheeltwine upload dist/*

8.3.设置文件 setup.py

# setup.py
from setuptools import setup, find_packages

setup(
    name="mypackage",
    version="1.0.0",
    description="一个示例包",
    author="Your Name",
    packages=find_packages(),
    install_requires=[
        'requests>=2.25.1',
        'numpy>=1.19.5',
    ],
    python_requires='>=3.6',
)

8.4.安装和发布

# 开发模式安装(可编辑模式)
pip install -e .

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

# 上传到PyPI
twine upload dist/*

9.最佳实践

9.1.命名规范

  • 模块命名:使用小写字母和下划线
  • 包命名:使用小写字母,避免使用下划线
  • 避免冲突:不与标准库同名

9.2.导入规范

  • 导入顺序:标准库 → 第三方库 → 本地模块
  • 避免循环导入:模块间相互导入
  • 使用 if __name__ == "__main__":区分直接运行和导入

9.3.导入顺序示例

# 标准库
import os
import sys
from datetime import datetime

# 第三方库
import requests
import numpy as np

# 本地模块
from mypackage import module1
from mypackage.subpackage import module2

9.4.项目组织

  • 合理使用包和模块
  • 避免过深的嵌套结构
  • 保持模块功能单一
  • 编写清晰的文档和注释
img