第12章 模块与包

4 阅读10分钟

第12章 模块与包

12.1 模块概述

Python 中一个以 .py 结尾的源文件即为一个模块(Module)。其中可以包含变量、函数和类等。通常情况下,我们把能够实现某一特定功能的代码放置在一个文件中作为一个模块。

使用模块提高了代码的可维护性,也提高了代码的复用性。即编写好一个模块后,只要是实现该功能的程序,都可以导入这个模块实现。另外,使用模块也可以避免名称冲突,相同名字的函数或变量可以分别存在与不同的模块中。

12.2 创建模块

模块名区分大小写,且不能与 Python 自带的标准模块重名。

创建一个模块 my_add.py

num = 100

def add(a, b):
    """求两个数的和"""
    return a + b

12.3 导入模块

12.3.1 全部导入 import

导入模块的所有成员,通过 模块名.成员名 的方式访问。即使多次使用 import 导入同一模块,模块也只会被导入一次。

1)语法

import 模块名 [as 别名]

2)案例

在同一目录下创建一个 main.py 文件,在其中导入 my_add.py 模块并使用。

# 导入模块
import my_add

# 使用模块
print(my_add.add(1, 2))
print(my_add.num)

也可以在导入模块时给模块起别名:

# 导入模块
import my_add as a1

# 使用模块
print(a1.add(1, 2))
print(a1.num)

12.3.2 局部导入 from import

指定导入模块的部分成员,直接通过成员名的方式访问。只能使用其导入的成员,未导入的成员不能使用。如果多个模块中存在重名成员,后一次导入会覆盖前一次导入。

1)语法

from 模块名 import 成员名1[as 别名], 成员名2[as 别名], …

2)案例

创建新的模块 my_multi.py

num = 200
_str1 = "abc"

def multi(a, b):
    """求两个数的积"""
    return a * b

只能使用导入的成员:

from my_add import add
print(add(1, 2))
print(num)  # NameError: name 'num' is not defined

重名变量,后一次导入会覆盖前一次导入:

# 导入模块
from my_add import add, num
from my_multi import num

# 使用模块
print(add(1, 2))
print(num)  # 200(来自my_multi)

通过别名区分不同模块的变量:

# 导入模块
from my_add import add, num as a1
from my_multi import num as m1

# 使用模块
print(add(1, 2))
print(a1)  # 100
print(m1)  # 200

12.3.3 局部导入 from import *

导入模块中所有不以单下划线开头的成员,直接通过成员名的方式访问。

1)语法

from 模块名 import *

2)案例

# 导入模块
from my_add import *

# 使用模块
print(add(1, 2))
print(num)
print(_str1)  # 以单下划线开头的成员不会被导入

12.3.4 模块搜索顺序

当导入一个模块时,会按照以下顺序进行查找:

  1. 当前目录。
  2. PYTHONPATH 环境变量中的目录。
  3. 包含标准 Python 模块以及这些模块所依赖的任何 extension module 的目录。

可以使用以下方式查看模块搜索顺序:

import sys
print(sys.path)

也可以通过 sys.path.append(路径)sys.path 中临时添加路径:

import sys
print(sys.path)
sys.path.append("./..")
print(sys.path)

12.3.5 __all__

使用 from import * 导入模块时,可以在被导入的模块中使用 __all__ 设置哪些内容可以被导入。__all__ 的设置只针对使用 from import * 导入模块时有效。

my_add.py 中向 __all__ 添加部分元素:

__all__ = ["num", "add"]  # 内容必须要用引号引起来
num = 100
num1 = 200
_str1 = "abc"

def add(a, b):
    """求两个数的和"""
    return a + b

main.py 中使用 from my_add import * 导入模块中的内容。没在 __all__ 中的变量在使用时会报错:

from my_add import *
print(add(1, 2))
print(num)
print(num1)  # NameError: name 'num1' is not defined

而使用 import my_add 全局导入模块后可以正常使用所有元素:

import my_add
print(my_add.add(1, 2))
print(my_add.num)
print(my_add.num1)  # 200

12.3.6 __name__

在 Python 中,__name__ 是一个特殊的内置变量:

  • 当一个 Python 文件被直接运行时,该文件的 __name__ 属性值为 "__main__"
  • 当一个 Python 文件作为模块被导入时,__name__ 属性会被设置为该模块的名称(即文件名,不包含 .py 后缀)。

1)导入模块时测试代码被执行

有时我们会在模块中写一些测试代码,当模块被其他文件导入时这些测试代码会被执行。

my_add.py 中写一些测试代码:

"""my_add.py"""
__all__ = ["num", "add"]
num = 100
num1 = 200
_str1 = "abc"

def add(a, b):
    """求两个数的和"""
    return a + b

print(add(10, 20))

main.py 中导入模块,发现 my_add.py 中的测试代码被执行了:

"""main.py"""
import my_add

2)使用 __name__ == "__main__" 避免测试代码被执行

为了避免模块被导入时测试代码被执行,我们可以在被导入模块中添加对 __name__ 属性的检查:

"""my_add.py"""
__all__ = ["num", "add"]
num = 100
num1 = 200
_str1 = "abc"

def add(a, b):
    """求两个数的和"""
    return a + b

print(__name__)
if __name__ == "__main__":
    print(add(10, 20))

此时再在 main.py 中导入模块,测试代码不会被执行:

"""main.py"""
import my_add

12.4 dir()

dir() 是一个内置函数,主要用于列出对象的属性和方法,或者列出当前作用域中定义的名称,并以一个字符串列表的形式返回。

  • 当你将一个模块作为 dir() 的参数时,它会返回该模块中定义的名称列表,包括函数、类、变量等:
import math
# 查看math模块下的内容
print(dir(math))
  • 当你将一个对象作为 dir() 的参数时,它会返回该对象的属性和方法列表:
class MyClass:
    def __init__(self):
        self.x = 1
        self.y = 2
    def method1(self):
        pass

obj = MyClass()
print(dir(obj))
  • 当你不传递任何参数调用 dir() 时,它会列出当前作用域中定义的名称,包括变量、函数、类等:
def my_function():
    pass

variable = 10
print(dir())

12.5 包概述

包是一种管理 Python 模块命名空间的形式。

通过使用 .模块名 来构造 Python 模块命名空间的一种方式。例如,模块名 A.B 表示名为 A 的包中名为 B 的子模块。通常我们将多个有联系的模块放入一个包中。包与文件夹相似,不过该文件夹下必须有一个 __init__.py 文件。

假设要为统一处理声音文件与声音数据设计一个模块集(包),下面这个分级文件树展示了这个包的架构:

sound/                          最高层级的包
      __init__.py               初始化 sound 包
      formats/                  用于文件格式转换的子包
              __init__.py
              wavread.py
              wavwrite.py
              aiffread.py
              aiffwrite.py
              auread.py
              auwrite.py
              ...
      effects/                  用于音效的子包
              __init__.py
              echo.py
              surround.py
              reverse.py
              ...
      filters/                  用于过滤器的子包
              __init__.py
              equalizer.py
              vocoder.py
              karaoke.py
              ...

12.6 创建包

__init__.py 可以只是一个空文件,也可以执行包的初始化代码或设置 __all__ 变量。

在 PyCharm 创建一个 graphic 文件夹,并在其中创建 circle.pyrectangle.py 文件。其中 __init__.py 文件暂时为空。

circle.py

"""circle.py"""
radius = 10
PI = 3.1415926

def area(radius):
    return PI * radius * radius

def perimeter(radius):
    return 2 * PI * radius

rectangle.py

"""rectangle.py"""
rectangle_width = 10
rectangle_height = 10

def area(width, height):
    return width * height

def perimeter(width, height):
    return 2 * (width + height)

在包外创建一个 main.py 文件。整体结构中,graphic 为一个包。

12.7 导入包

12.7.1 全局导入 import

导入包中模块的所有成员。

1)语法

import 包名.模块名 [as 别名]

2)调用方式:包名.模块名.成员名

import graphic.circle
print(graphic.circle.area(10))  # 314.15926

注意:

使用 import 时,除最后一项外都必须是包。最后一项可以是模块或包,但不能是类、函数或变量。

如果最后一项是包,那么必须在被导入包的 __init__.py 文件中,指定导入包中的哪些模块。这是 Python 导包的一个优化机制,避免导入过多模块。并且在 __init__.py 中指定导入模块的时候,建议使用相对路径

from . import circle

12.7.2 局部导入包下的模块 from import

从包中导入模块。

1)语法

from 包名 import 模块名 [as 别名]

2)调用方式:模块名.成员名

from graphic import circle
print(circle.area(10))  # 314.15926

12.7.3 局部导入包下模块的成员 from import

从包中模块导入功能。

1)语法

from 包名.模块名 import 成员名 [as 别名]

2)调用方式:成员名

from graphic.circle import area
print(area(10))  # 314.15926

12.7.4 局部导入 from import * 从包中导入模块

当我们使用 from import * 时,Python 并不会查找并导入包的所有子模块,因为这将花费很长的时间,并且可能会产生我们不想要的副作用。

唯一的解决办法是提供包的显式索引。如果包的 __init__.py 中定义了 __all__,运行 from import * 时,它就是被导入的模块名列表。

1)语法

from 包名.模块名 import *

2)调用方式:模块名.功能名

__init__.py 中添加如下内容:

__all__ = ["circle"]

注意:如果不加会无法导包

main.py 中使用 from import * 导入模块:

from graphic import *
print(circle.area(10))  # 314.15926
print(rectangle.area(10))  # 报错

12.8 常用标准库(包)

标准库指的是在安装 Python 时就一同被安装的库。这些库经过精心挑选和开发,旨在为 Python 开发者提供通用且强大的工具集,涵盖各种不同的应用领域。

名称说明
os多种操作系统接口。
sys系统相关的形参和函数。
time时间的访问和转换。
datetime提供了用于操作日期和时间的类。
math数学函数。
random生成伪随机数。
re正则表达式匹配操作。
jsonJSON 编码器和解码器。
collections实现了一些专门化的容器,提供了对 Python 的通用内建容器 dict、list、set 和 tuple 的补充。
functools高阶函数,以及可调用对象上的操作。
hashlib安全哈希与消息摘要。
urllibURL 处理模块。
smtplibSMTP 协议客户端,邮件处理。
zlib与 gzip 兼容的压缩。
gzip对 gzip 文件的支持。
bz2对 bzip2 压缩算法的支持。
multiprocessing基于进程的并行。
threading基于线程的并行。
copy浅层及深层拷贝操作。
socket低层级的网络接口。
shutil提供了一系列对文件和文件集合的高阶操作,特别是提供了一些支持文件拷贝和删除的函数。
globUnix 风格的路径名模式扩展。

更多标准库可参考 docs.python.org/zh-cn/3/lib…

12.9 引入第三方库

当需要使用 Python 中没有内置的库时,可以通过以下方式安装第三方库。

12.9.1 pip 命令方式

pip 是 Python 包管理工具,该工具提供了对 Python 包的查找、下载、安装、卸载的功能。pip 默认的源是 Python Package Index(PyPI),其地址为 pypi.org/simple/,如果下…

1)pip 常用命令

  • 查看我们已经安装的软件包:pip list
  • 安装软件包:pip install 包名
  • 卸载软件包:pip uninstall 包名
  • 临时使用其他源:pip install -i http://mirrors.aliyun.com/pypi/simple/ 包名
  • 永久修改源:pip config set global.index-url http://mirrors.aliyun.com/pypi/simple/

2)安装 requests 包步骤

通过命令行的方式安装,是将第三方包安装在本地 Python 下,例如:D:\dev\software\Python3.12.8\Lib\site-packages

12.9.2 PyCharm 中引入

  1. 点击右下角的解释器设置
  2. 点击 + 号
  3. 搜索要添加的包

通过 PyCharm 安装,我们选择的解释器类型每个项目独立的,所以是将第三方包安装在当前项目环境下,例如:D:\dev\workspace\python-2025\.venv\Lib\site-packages

12.10 打包自己的库并安装

1)先安装 setuptools 库

如果不安装 setuptools 库,后续打包时可能会遇到报错 ModuleNotFoundError: No module named 'distutils',所以可以提前安装 setuptools 库。在命令提示符中执行如下命令:

pip install setuptools

2)在包外创建一个 setup.py 文件

3)setup.py 中添加如下内容

from distutils.core import setup

setup(
    name="graphic",  # 需要打包的名字
    version="1.0",  # 版本
    py_modules=["graphic.circle", "graphic.rectangle"],  # 需要打包的模块
)

4)在 setup.py 同级目录下进行构建

5)也可以生成压缩包

python setup.py sdist

6)pip 命令安装自己打的库

pip install path_to_your_package/dist/your_package_name-0.1.tar.gz

7)PyCharm 安装自己打的包库

在 PyCharm 的包管理界面中,可以选择从本地安装打好的包。