10模块与包

10模块与包

_

注:学习资料来源<尚硅谷>

视频:点击跳转

代码仓库:pythonStudy

在 Python 学习与项目开发过程中,“模块”和“包”是两个绕不开的核心概念。无论是编写小型脚本,还是构建大型工程,合理地组织代码结构,都会直接影响程序的可维护性、可复用性以及团队协作效率。

很多初学者刚接触 Python 时,往往只关注语法本身,例如变量、流程控制、函数、类等内容,而容易忽略代码组织层面的设计。但随着代码规模逐渐扩大,如果仍然将所有逻辑堆积在一个 .py 文件中,就会很快遇到命名冲突、功能耦合、复用困难、维护成本上升等问题。此时,模块与包的价值便会真正体现出来。

本文将系统梳理 Python 中模块与包的核心概念、导入方式、命名规范、__all____name__ 的作用、标准库与第三方包的区别,以及全局环境与虚拟环境的实际使用方式,帮助你建立一套清晰完整的知识体系。


一、什么是模块

在 Python 中,一个 .py 文件本质上就是一个模块(Module)。也就是说,只要你创建了一个 Python 文件,这个文件天然就具备模块的属性。

模块中可以包含多种程序元素,例如变量、函数、类、可执行语句等。通常情况下,我们会把某一类相关功能集中封装到一个模块中,使其承担单一职责。这样的设计方式,实际上就是将模块作为功能单元进行管理。

从工程视角来看,模块的主要作用体现在以下几个方面。

首先,它能够提高代码的可维护性。不同功能被拆分到不同模块后,开发者可以更容易定位问题、修改逻辑和扩展功能。其次,它提升了代码的可复用性。某个模块只要设计得当,就可以在多个项目中复用,而不需要重复编写相同逻辑。最后,模块还能在一定程度上避免命名冲突,因为不同模块中的同名变量或函数可以通过模块名加以区分。

可以把模块理解为一个“工具箱”。当你需要某项功能时,不必重新制造工具,而是直接导入对应模块进行调用。


二、模块的分类

Python 中的模块大致可以分为三类:标准库模块、自定义模块和第三方模块。

标准库模块是 Python 安装后默认提供的模块,它们构成了 Python 的基础能力体系,覆盖文件操作、时间处理、数学计算、系统交互、随机数生成等众多常见场景。比如 ossysmathtimerandom 等都属于这一类。

自定义模块则是开发者自己编写的 .py 文件。它们往往用于承载业务逻辑,是实际项目中最常见的模块形式。

第三方模块是由社区或组织开发并发布的扩展模块,通常通过 pip 工具安装。例如数据分析常用的 numpy、文本处理常用的 jieba、模拟数据生成常用的 faker 等,都属于第三方模块。

从实际开发角度来说,这三类模块共同构成了 Python 程序的能力来源:标准库提供基础设施,自定义模块承载业务逻辑,第三方模块负责扩展生态能力。


三、如何创建自定义模块

创建模块本身非常简单,本质上就是新建一个 .py 文件。但在命名时需要遵循一定规范。

第一,模块名必须符合 Python 标识符命名规则。第二,模块名区分大小写,因此命名时应保持风格统一。第三,不要让自定义模块与标准库模块重名,否则会导致导入行为产生歧义,甚至覆盖标准库模块,带来调试困难。

假设我们创建两个模块,分别用于处理订单和支付逻辑:

 # order.py
 ​
 # 订单最大金额
 max_order_amount = 1000000
 ​
 # 创建订单
 def create_order():
     print('订单创建成功!')
 ​
 # 关闭订单
 def cancel_order():
     print('订单关闭成功!')
 ​
 # 提示函数
 def show_info():
     print('我是来自【订单】模块的提示!')
 # pay.py
 ​
 # 支付超时时间
 timeout = 1800
 ​
 # 微信支付
 def wechat_pay():
     print('我是微信支付!')
 ​
 # 支付宝支付
 def ali_pay():
     print('我是支付宝支付!')
 ​
 # 提示函数
 def show_info():
     print('我是来自【支付】模块的提示!')

从这个简单示例可以看出,订单相关逻辑被集中放在 order.py 中,支付相关逻辑被集中放在 pay.py 中。这样的拆分方式本质上就是模块化设计,它让程序结构更加清晰,也更符合单一职责原则。


四、模块的常见导入方式

Python 提供了多种导入模块的语法形式,不同形式适用于不同场景。理解这些导入方式,不仅有助于代码编写,也有助于理解命名空间和作用域的关系。

1. 使用 import 模块名

这是最基础、最常见的导入方式。导入后,需要通过“模块名.成员名”的形式来访问模块中的内容。

 import order
 import pay
 ​
 print(order.max_order_amount)
 order.create_order()
 order.cancel_order()
 order.show_info()
 ​
 print('*' * 10)
 ​
 print(pay.timeout)
 pay.wechat_pay()
 pay.ali_pay()
 pay.show_info()

这种写法的优点是语义清晰,调用来源明确,代码可读性较高。尤其在大型项目中,这种“显式命名空间”的方式非常推荐,因为它能有效降低命名冲突的风险。

需要特别注意的是,Python 在导入模块时,会执行该模块中的顶层代码。另外,模块在同一解释器进程中通常只会被加载一次,后续重复导入时会直接复用缓存,而不会再次完整执行。


2. 使用 import 模块名 as 别名

当模块名较长,或者希望使用更简洁的调用方式时,可以为模块设置别名。

 import order as dd
 import pay as zf
 ​
 print(dd.max_order_amount)
 dd.create_order()
 dd.cancel_order()
 dd.show_info()
 ​
 print('*' * 10)
 ​
 print(zf.timeout)
 zf.wechat_pay()
 zf.ali_pay()
 zf.show_info()

这种方式在实际开发中很常见,特别是某些第三方库通常约定俗成地使用别名,例如:

 import numpy as np
 import pandas as pd

别名的使用能够提高输入效率,但也要适度。如果别名过于随意,反而会降低代码可读性。因此建议使用语义明确、行业常见或团队统一约定的别名。


3. 使用 from 模块名 import 成员

如果只需要模块中的某几个成员,可以采用按需导入的方式。

 from order import max_order_amount, show_info
 from pay import wechat_pay, ali_pay
 ​
 print(max_order_amount)
 show_info()
 wechat_pay()
 ali_pay()

这种写法的好处是调用时不需要再加模块名前缀,代码更简洁。但它也会把导入的成员直接放入当前命名空间,因此在成员较多或不同模块存在同名对象时,容易出现命名冲突。

例如上面的 orderpay 模块中都定义了 show_info(),如果分别导入两个同名函数,就会产生覆盖问题。因此在实际使用中,要结合具体场景谨慎选择。


4. 使用 from 模块名 import 成员 as 别名

如果导入的成员名过长,或者存在重名风险,可以进一步为成员设置别名。

 from order import max_order_amount as max_amt, show_info as show1
 from pay import timeout as tm, show_info as show2
 ​
 print(max_amt)
 print(tm)
 show1()
 show2()

这种写法兼顾了简洁性与冲突规避能力,在处理重复函数名、重复常量名时非常实用。


5. 使用 from 模块名 import *

这种方式表示导入模块中的所有可导出成员。

 from order import *
 from pay import *
 ​
 max_order_amount = 10
 print(max_order_amount)
 create_order()
 cancel_order()
 show_info()
 print(timeout)
 wechat_pay()
 ali_pay()
 show_info()

虽然这种写法看起来最省事,但通常并不推荐在正式项目中大量使用。原因在于它会把模块中的大量名称直接导入当前命名空间,极易造成变量覆盖、函数冲突和可读性下降。尤其当多个模块中存在同名成员时,后导入的内容可能覆盖先导入的内容,埋下潜在 bug。

因此,import * 更适合教学演示或非常明确的小范围场景,而不适合中大型工程。


五、__all____name__ 的作用

在学习模块时,__all____name__ 是两个非常重要的内置机制。

1. __all__

__all__ 用于控制 from 模块 import * 时,哪些成员会被导入。它通常是一个列表或元组。

例如:

__all__ = ['create_order', 'cancel_order']

如果模块中定义了以上内容,那么使用 from order import * 时,只会导入 create_ordercancel_order,而不会导入其他变量或函数。

从接口设计角度看,__all__ 相当于定义了模块的“公开 API”,它可以帮助开发者明确哪些对象是希望暴露给外部使用的。


2. __name__

__name__ 是每个 Python 模块都自动拥有的内置变量,它的值取决于模块的运行方式。

如果一个模块作为主程序直接执行,那么 __name__ 的值就是 __main__。如果一个模块是被其他模块导入执行,那么 __name__ 的值就是该模块文件名(不包含 .py)。

这也是为什么很多 Python 文件中会出现如下写法:

if __name__ == '__main__':
    print('当前文件作为主程序运行')

这种写法的意义在于:只有当文件被直接运行时,这段代码才会执行;如果文件只是作为模块被导入,则不会执行。这是非常经典且重要的模块测试与入口控制手段。


六、标准库模块与内置模块

Python 标准库是其“开箱即用”能力的重要体现。安装 Python 后,就已经自带大量成熟模块,开发者可以直接使用。

不过,标准库模块内部还可以进一步区分为“非内置模块”和“内置模块”。

非内置模块通常以 .py 文件形式存在于 Python 安装目录的 Lib 文件夹下,因此可以通过 __file__ 属性查看它们的物理路径。例如 copyosrandom 等。

import copy
import os
import random

# print(copy.__file__)
# print(os.__file__)
# print(random.__file__)

而内置模块通常由 C 语言实现,直接编译进 Python 解释器中,因此往往没有可直接查看的源文件路径,也没有 __file__ 属性。例如 timemathsys 等就属于典型的内置模块。

下面是一些常见标准库模块的示例:

import os
import random
import time
import math
import sys

# 创建文件夹
os.mkdir('demo')

# 随机选择
names = ['张三', '李四', '王五', '李华']
print(random.choice(names))

# 洗牌
random.shuffle(names)
print(names)

# 休眠
time.sleep(2)
print('ok')

# 格式化时间
print(time.strftime('%H:%M:%S'))
print(time.strftime('%p %I:%M:%S'))

# 数学运算
print(math.sqrt(4))
print(math.fabs(-11.2))

# Python 解释器版本
print(sys.version)

标准库的广度非常大,熟练掌握常用模块,能够显著提高开发效率。在很多场景下,合理利用标准库就足以解决大量问题,无需额外引入第三方依赖。


七、什么是包

如果说模块是代码组织的基本单位,那么包(Package)就是更高层级的组织结构。包的出现,是为了管理多个相关模块,并形成更清晰的层次体系。

在 Python 中,通常把“包含 __init__.py 文件的目录”视为一个包。包中不仅可以包含多个模块,还可以包含子包以及其他资源文件。

模块适合组织单个功能单元,而包适合组织一组相关模块。例如,订单模块、支付模块、退款模块、发票模块可以统一纳入一个电商交易包中进行管理。随着项目规模扩大,包的价值会越来越明显。

从架构角度理解,模块解决的是“功能拆分”问题,包解决的是“结构管理”问题。


八、模块与包的关系

模块和包常常一起出现,但两者并不是同一个概念。

模块本质上是一个 .py 文件,它是最基本的代码载体。包本质上是一个目录,它用于管理多个模块乃至多个子包。

换句话说,包是模块的容器。一个包中可以有多个模块,也可以有多个子包;而一个模块通常专注于一类具体逻辑。

这种层次结构使得 Python 可以自然支持大型工程的目录化组织,让代码从“文件级复用”进一步提升为“目录级、系统级复用”。


九、如何创建包

创建包的方式也很直观。只需要创建一个文件夹,并在其中放入 __init__.py 文件,该目录便具备了包的属性。

例如,创建一个名为 trade 的包:

trade/
├── __init__.py
├── order.py
└── pay.py

其中 order.pypay.py 的内容可以与前面的模块示例保持一致,而 __init__.py 初始时可以为空。

在命名包时,同样需要遵循标识符命名规则,且建议统一使用小写字母。同时,应避免与标准库包或第三方包同名,以免影响导入。


十、包的导入方式

包的导入方式与模块类似,但需要带上层级路径。理解这些写法,对于阅读真实项目代码非常重要。

1. 使用 import 包名.模块名

import trade.order
import trade.pay

trade.order.create_order()
trade.pay.wechat_pay()

这种方式保留了完整命名空间,适合强调调用来源的场景。


2. 使用 import 包名.模块名 as 别名

import trade.order as dd
import trade.pay as zf

dd.create_order()
zf.wechat_pay()

适合路径较长时简化调用。


3. 使用 from 包名.模块名 import 成员

from trade.order import max_order_amount, create_order
from trade.pay import timeout, wechat_pay

print(max_order_amount)
print(timeout)
create_order()
wechat_pay()

这种方式可实现精确导入,提高代码简洁性。


4. 使用 from 包名.模块名 import 成员 as 别名

from trade.order import max_order_amount as max_amt, create_order
from trade.pay import timeout, wechat_pay as w_pay

print(max_amt)
print(timeout)
create_order()
w_pay()

当名称较长或存在重名时,这种写法非常实用。


5. 使用 from 包名.模块名 import *

from trade.order import *
from trade.pay import *

create_order()
cancel_order()
show_info()
print(timeout)
wechat_pay()
ali_pay()
show_info()

其缺点与模块中的通配导入相同:命名污染严重,不利于维护。


6. 使用 from 包名 import 模块名

from trade import order, pay

order.create_order()
pay.wechat_pay()

这种方式在项目中较常见,既保留了模块边界,又避免完整书写包路径,平衡了可读性与便捷性。


7. 使用 from 包名 import 模块名 as 别名

from trade import order as dd, pay as p

dd.create_order()
p.wechat_pay()

适合在局部范围内缩短命名。


十一、__init__.py 的实际意义

很多初学者只知道包中需要有 __init__.py,但并不清楚它究竟有什么作用。事实上,__init__.py 并不是一个“占位文件”那么简单,它是包的初始化文件。

当包被导入时,__init__.py 会自动执行。因此,你可以在其中编写一些包级别的初始化逻辑,例如导入子模块、定义常量、暴露统一接口等。

例如,在 trade/__init__.py 中可以写:

from . import order
from . import pay

a = 100
b = 200

__all__ = ['order', 'pay', 'a', 'b']

这样便可以支持如下用法:

from trade import *
print(order.max_order_amount)
print(pay.timeout)
print(a)
print(b)

甚至还可以直接通过以下方式访问:

import trade

print(trade.a)
print(trade.b)
trade.order.create_order()
trade.pay.wechat_pay()

也就是说,如果希望通过 import 包名 的方式直接访问某些包内成员,就需要提前在 __init__.py 中进行明确导出。

因此,__init__.py 的本质作用有三点:第一,标识当前目录为包;第二,定义包的初始化逻辑;第三,控制包的公共接口暴露。


十二、子包的引入

在更复杂的项目中,包内部还可以继续划分子包,以形成更细粒度的目录结构。

例如,在 trade 包中再创建一个 hello 子包:

trade/
├── __init__.py
├── order.py
├── pay.py
└── hello/
    ├── __init__.py
    └── h1.py

其中 h1.py 内容如下:

def say_hello():
    print('你好')

导入时可以使用完整路径:

from trade.hello.h1 import say_hello
say_hello()

这说明 Python 的包机制天然支持分层结构,非常适合大型项目的分模块设计。例如 Web 项目中常见的 controllersservicesmodelsutils 等目录,本质上都可以通过包与子包来组织。


十三、第三方包与 PyPI

除了标准库和自定义模块外,Python 生态中最具活力的部分就是第三方包。第三方包由社区开发者、企业组织或开源团队维护,功能覆盖机器学习、数据分析、Web 开发、自动化运维、图像处理、自然语言处理等各个方向。

Python 官方推荐的包发布平台是 PyPI(Python Package Index),网址为:

https://pypi.org

PyPI 可以理解为 Python 世界的软件仓库。开发者可以将自己编写的包发布到该平台,其他用户则可以通过 pip 工具完成检索、下载、安装与升级。

pip 是 Python 的官方包管理工具,常用命令包括:

pip install 包名
pip install -i 镜像地址 包名
pip config set global.index-url 地址
pip config list
pip config unset global.index-url
pip list
pip uninstall 包名

在国内网络环境下,访问官方源有时速度较慢,因此很多开发者会使用镜像源。例如:

  • 清华大学镜像:https://pypi.tuna.tsinghua.edu.cn/simple

  • 阿里云镜像:https://mirrors.aliyun.com/pypi/simple

  • 中国科学技术大学镜像:https://pypi.mirrors.ustc.edu.cn/simple

例如安装某个第三方包时,可以这样写:

pip install -i https://pypi.tuna.tsinghua.edu.cn/simple numpy

第三方包极大丰富了 Python 的应用场景,但也意味着项目依赖管理变得更加重要。这就自然引出了“环境”的概念。


十四、什么是 Python 环境

所谓 Python 环境,可以简单理解为:Python 解释器 + 依赖包集合

在实际开发中,Python 环境通常分为两类:全局环境和虚拟环境。

全局环境指系统级安装的 Python 及其依赖包。默认情况下,直接使用 pip install 安装的第三方包,通常都会进入全局环境。

虚拟环境则是在某个项目目录下独立创建的一套 Python 运行环境,它拥有自己的解释器副本、pip 工具以及独立安装的第三方依赖。不同项目之间的虚拟环境互不干扰,可以有效避免依赖冲突。

这一点在真实开发中极其重要。假设项目 A 依赖 numpy==1.23,项目 B 依赖 numpy==2.x,如果二者都安装在全局环境中,很容易发生版本冲突。而虚拟环境则允许两个项目拥有各自独立的依赖树,从而实现隔离。

需要注意的是,虚拟环境和全局环境通常共享标准库,但第三方依赖是彼此独立的。


十五、全局环境中的第三方包使用示例

在全局环境中安装好 numpypyfiglet 后,可以直接在项目中使用。

import numpy as np

result = np.random.randint(10, 100, size=10)
print(result)
print(type(result))
print(result.max())
print(result.min())

这段代码演示了 numpy 在随机数组生成与数组运算方面的能力。numpy.ndarray 作为高性能数组对象,是科学计算生态的核心基础之一。

再看一个 pyfiglet 示例:

from pyfiglet import figlet_format

result = figlet_format('hello')
print(result)

该包可以将普通字符串渲染为 ASCII 艺术字,常用于命令行工具的美化展示。

与此同时,全局环境中的项目仍然可以正常使用标准库:

from collections import Counter

list1 = [10, 20, 30, 40, 20, 30, 20, 30, 10, 10, 10]
res = Counter(list1)
print(res)

Counter 是标准库中用于计数统计的常用工具,体现了标准库“即装即用”的优势。

因此可以得出结论:基于全局环境创建的项目,既可以使用全局环境中安装的第三方包,也可以使用标准库。


十六、虚拟环境中的第三方包使用示例

与全局环境不同,虚拟环境强调“项目隔离”。每个虚拟环境都有独立的依赖空间,因此需要在当前环境中单独安装所需包。

在虚拟环境中,可以通过命令行方式安装第三方包,也可以借助开发工具的终端或图形界面安装。例如在某个虚拟环境中安装 fakerjiebacn2an 后,可以进行如下测试。

from faker import Faker

f = Faker('zh_CN')
print(f.name())
print(f.address())
print(f.phone_number())

faker 常用于测试数据生成,能够快速模拟姓名、地址、电话等结构化信息,特别适合接口联调、功能测试和演示环境搭建。

import jieba

result = jieba.lcut('南京市长江大桥')
print(result)

jieba 是中文分词领域的常用工具。该示例展示了自然语言处理中常见的文本切分任务,体现了 Python 在中文文本处理方面的生态优势。

import cn2an

print(cn2an.cn2an('九千七百四十三'))

cn2an 用于中文数字与阿拉伯数字之间的转换,适用于金融、报表、文本解析等业务场景。

同时,虚拟环境中的项目依然可以继续使用标准库:

from collections import Counter

list1 = [10, 20, 30, 40, 20, 30, 20, 30, 10, 10, 10]
res = Counter(list1)
print(res)

因此同样可以得出结论:基于虚拟环境的项目,可以使用虚拟环境中安装的第三方包,也可以正常使用标准库。


十七、为什么在真实项目中更推荐虚拟环境

虽然全局环境使用起来简单直接,但在项目实践中,更推荐优先使用虚拟环境。这并不是因为全局环境“不能用”,而是因为虚拟环境在工程管理层面更可靠。

首先,虚拟环境能够实现依赖隔离。不同项目彼此独立,不会因为安装、升级、卸载某个库而相互影响。其次,虚拟环境有助于项目复现。当团队成员共享依赖清单时,可以更轻松地在不同机器上还原一致的开发环境。再次,虚拟环境能降低运维与部署风险,特别是在项目版本长期演进的情况下,其价值会越来越明显。

可以说,在教学示例中,全局环境足够直观;而在实际开发中,虚拟环境几乎是标准配置。


十八、模块、包与环境之间的关系总结

从知识体系的角度来看,模块、包和环境并不是孤立的概念,而是构成 Python 项目结构管理的三个层次。

模块解决的是“代码如何拆分”的问题。它让功能具备独立边界,使逻辑复用成为可能。

包解决的是“模块如何组织”的问题。它让多个模块能够形成层次化结构,适应更复杂的工程规模。

环境解决的是“依赖如何隔离”的问题。它让项目之间的解释器和第三方包互不干扰,从而保障开发、测试、部署的一致性。

如果把 Python 项目比作一栋建筑,那么模块是房间,包是楼层,而环境则是整栋楼的基础设施系统。只有三者配合合理,项目结构才能真正稳定、清晰、可扩展。


十九、学习建议与实践建议

对于初学者来说,理解模块与包时,不建议只停留在语法层面,而应尽早建立工程化思维。可以从以下几个方向逐步实践。

第一,写代码时有意识地将不同功能拆分到不同模块中,而不要把所有内容堆积在一个文件里。第二,在模块数量逐渐增多后,尝试使用包进行目录归类,例如把“用户相关”“订单相关”“支付相关”的模块分别纳入独立包中。第三,养成阅读标准库文档和第三方包文档的习惯,尽量优先使用成熟工具,而不是重复造轮子。第四,在做任何稍微正式一点的项目时,都优先创建虚拟环境,并记录依赖版本。

当你真正开始关注代码组织方式时,Python 的开发体验会发生明显变化。你会发现,写程序不再只是“把功能实现出来”,而是在思考“如何让代码更清晰、更稳定、更易扩展”。


二十、结语

模块与包是 Python 语言中非常基础但又极其重要的组成部分。它们并不只是几种导入语法那么简单,而是贯穿代码复用、命名空间管理、项目分层、依赖治理等多个维度的核心机制。

掌握模块,你就掌握了代码拆分与复用的基本方法;掌握包,你就掌握了项目结构设计的核心手段;理解环境管理,你就具备了走向工程化开发的关键能力。

对于任何希望从“会写 Python 脚本”进阶到“能够构建 Python 项目”的学习者来说,模块、包与环境管理都是必须扎实掌握的基础。

09错误与异常 2026-04-25

评论区