Python模块:导入、自定义模块、包与第三方库安装

学完函数、容器、高级特性和函数式编程之后,接下来要解决一个非常实际的问题:当代码越来越多时,应该如何组织它们?

如果把所有函数、变量和业务逻辑都堆在一个 .py 文件里,刚开始也许还能勉强维护,但一旦项目变大,代码就会变得难读、难改、难复用。这时,模块(module)包(package) 就成为 Python 编程里非常重要的组织方式。

本文会围绕以下几个问题展开:

  • 什么是模块,为什么要使用模块;
  • import 到底做了什么;
  • 如何编写自己的模块;
  • 什么是包,如何用目录组织模块;
  • Python 是如何查找模块的;
  • 如何安装和使用第三方模块。

理解这一章之后,你就不再只是“会写一个 Python 文件”,而是开始具备组织一个小型 Python 项目的能力。

一、为什么需要模块?

在前面的学习里,我们写的大多数代码都放在一个文件中,例如:

1
2
3
4
print('Hello, Python!')

def add(x, y):
return x + y

这种写法在入门阶段完全没问题,但如果项目逐渐变大,就会遇到几个现实问题:

  1. 代码难以维护:一个文件过长后,查找逻辑会越来越痛苦;
  2. 功能难以复用:如果别的程序也想使用其中某个函数,复制粘贴显然不是好办法;
  3. 命名容易冲突:大家都把函数写在一个文件里,名字重复的概率会越来越高;
  4. 协作困难:多人开发时,不同功能最好拆成不同文件,否则冲突频繁。

这时就需要把代码拆分成多个模块。

你可以把模块理解为:

一个 .py 文件,就是一个 Python 模块。

例如:

  • math_utils.py
  • string_tools.py
  • user_service.py

它们都可以被其他 Python 文件导入并使用。


二、什么是模块?

最简单地说,一个 Python 源文件就是一个模块

例如我们创建一个文件 greet.py

1
2
3
4
5
6
def say_hello(name):
print(f'Hello, {name}!')


def say_bye(name):
print(f'Bye, {name}!')

然后在另一个文件 main.py 中导入它:

1
2
3
4
import greet

greet.say_hello('Alice')
greet.say_bye('Alice')

运行 main.py 时,就可以使用 greet 模块中定义的函数。

2.1 使用模块的核心价值

模块最大的价值不只是“把代码拆开”,而是让程序具备更好的结构。

例如,一个小项目可以这样划分:

1
2
3
4
5
project/
├── main.py
├── db.py
├── user.py
└── utils.py

其中:

  • main.py 负责程序入口;
  • db.py 负责数据库相关逻辑;
  • user.py 负责用户业务;
  • utils.py 负责通用工具函数。

这样一来,职责会清晰很多。

2.2 模块和函数的关系

函数解决的是“复用一段逻辑”;
模块解决的是“组织一组相关代码”。

你可以这样理解:

  • 函数:代码复用的最小单位;
  • 模块:代码组织的基本单位;
  • :更高层级的模块组织方式。

三、import 到底做了什么?

学习模块时,最常见的语法就是 import

3.1 最基本的导入方式
1
2
3
4
import math

print(math.sqrt(9))
print(math.pi)

这里:

  • import math 表示导入标准库中的 math 模块;
  • 后续通过 math.sqrt()math.pi 访问模块里的函数和变量。
3.2 为什么导入后要加模块名前缀?

因为 import math 导入的是整个模块对象。

所以你拿到的是:

  • 模块名 math
  • 模块里的成员 sqrt
  • 模块里的成员 pi

也就是说:

1
math.sqrt(16)

本质上是在“模块命名空间”里找 sqrt

这样做有两个明显好处:

  1. 可读性好:你一眼就知道 sqrt 来自 math
  2. 避免冲突:不同模块里都可能有同名函数。

例如:

1
2
import random
import math

即使两个模块里以后都出现某个同名标识符,也不会直接打架,因为访问时都带模块前缀。

3.3 from ... import ... 的写法

有时你不想每次都写模块名前缀,可以这样导入:

1
2
3
4
from math import sqrt, pi

print(sqrt(25))
print(pi)

这样写更短,但也要注意:

  • 导入的名字会直接进入当前命名空间;
  • 如果导入太多,容易造成名字冲突;
  • 阅读代码时,不如 math.sqrt() 那样直观。

所以工程实践里通常建议:

  • 模块级功能较多时,优先用 import module
  • 只使用少量固定成员时,可以用 from module import name
3.4 起别名:as

如果模块名太长,或者想避免冲突,可以起别名:

1
2
import numpy as np
import pandas as pd

这在数据分析领域几乎是约定俗成的写法。

普通模块也一样:

1
2
3
import datetime as dt

print(dt.datetime.now())

对于导入成员,也能起别名:

1
2
3
from math import sqrt as square_root

print(square_root(36))
3.5 不推荐滥用 from module import *

你可能还见过这样的写法:

1
from math import *

这会把模块里的很多名字直接导入当前作用域。

虽然写起来省事,但问题很明显:

  • 你不容易知道某个名字来自哪里;
  • 容易覆盖当前已有变量;
  • 在大型项目里很难维护。

所以通常不建议在正式代码里使用这种写法。


四、自定义模块:把自己的代码拆出去

理解模块最好的方式,就是自己写一个模块。

4.1 一个简单的示例

新建文件 calc_utils.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def add(x, y):
return x + y


def sub(x, y):
return x - y


def mul(x, y):
return x * y


def div(x, y):
if y == 0:
raise ValueError('除数不能为 0')
return x / y

然后在 main.py 中使用它:

1
2
3
4
import calc_utils

print(calc_utils.add(3, 5))
print(calc_utils.mul(4, 6))
4.2 模块执行与模块导入的区别

很多初学者会疑惑:

一个文件既可以直接运行,也可以被别人导入,那该怎么区分?

这就要用到 __name__

1
2
3
4
5
def test():
print('这是测试函数')


print(__name__)
  • 如果你直接运行这个文件,__name__ 的值是 __main__
  • 如果这个文件被别的文件导入,__name__ 的值通常是模块名。

所以常见写法是:

1
2
3
4
5
6
def test():
print('这是测试函数')


if __name__ == '__main__':
test()

这表示:

  • 直接运行当前文件时,执行 test()
  • 把当前文件作为模块导入时,不执行这段测试代码。

这是 Python 模块里非常重要的一种写法。

4.3 一个更完整的模块示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

'一个简单的问候模块'

__author__ = 'Edenzeng'


def greet(name):
print(f'Hello, {name}!')


def main():
greet('Python')


if __name__ == '__main__':
main()

这里还出现了几个常见内容:

  • 模块说明字符串;
  • __author__ 作者信息;
  • main() 作为程序入口;
  • if __name__ == '__main__': 作为可执行判断。

这些内容不是强制要求,但在教程和示例代码里非常常见。


五、模块中的作用域与“公开 / 非公开”约定

模块中定义的变量、函数,本质上也有“是否希望给外部使用”的区别。

5.1 正常命名:默认可被外部访问
1
2
def public_func():
print('这是公开函数')

这种名字没有特殊限制,导入模块后可以直接访问。

5.2 以下划线开头:表示内部使用
1
2
def _private_func():
print('这是内部函数')

在 Python 中,以下划线开头是一种约定

  • public_func():公开接口;
  • _private_func():内部实现细节,不建议模块外直接使用。

注意,这不是“强制私有”,只是社区约定。

也就是说你仍然可以这样调用:

1
2
import mymodule
mymodule._private_func()

语法上没问题,但从设计上不推荐。

5.3 双下划线并不是模块级常规做法

__name____author____doc__ 这种双下划线名字,通常是 Python 预留或约定的特殊变量。

在模块里,我们更常见的是:

  • __name__
  • __file__
  • __doc__
  • __author__

而不是自己大量造双下划线名字。

5.4 对外暴露接口的思路

模块设计时,一个很好的习惯是:

  • 把对外提供的能力放在公开函数中;
  • 把内部辅助逻辑写成 _helper() 这类内部函数。

例如:

1
2
3
4
5
6
7
def _format_name(name):
return name.strip().title()


def greet(name):
formatted = _format_name(name)
print(f'Hello, {formatted}!')

这样模块边界会更清晰。


六、包(Package):用目录组织多个模块

当模块继续变多时,只靠平铺多个 .py 文件还不够,这时就要使用包。

6.1 什么是包?

简单理解:

包就是包含多个模块的目录结构。

例如:

1
2
3
4
mycompany/
├── __init__.py
├── abc.py
└── xyz.py

这里:

  • mycompany 是一个包;
  • abc.pyxyz.py 是这个包里的模块。

那么它们的模块名就变成了:

  • mycompany.abc
  • mycompany.xyz
6.2 为什么要有 __init__.py

在传统习惯里,一个目录下放上 __init__.py,Python 就会把它识别为包。

例如:

1
2
3
4
shop/
├── __init__.py
├── user.py
└── order.py

导入时可以这样写:

1
2
import shop.user
import shop.order

或者:

1
from shop import user

虽然在现代 Python 中,命名空间包让某些场景不一定强制需要 __init__.py,但对于初学者和普通项目来说,保留 __init__.py 依然是更清晰、更稳妥的做法

6.3 多层包结构

项目稍大一点时,经常会使用多层目录:

1
2
3
4
5
6
7
mycompany/
├── __init__.py
├── utils.py
└── web/
├── __init__.py
├── api.py
└── views.py

这时模块名分别可能是:

  • mycompany.utils
  • mycompany.web.api
  • mycompany.web.views

这种层级结构有助于把不同领域的代码拆开。

6.4 包的实际意义

包的价值主要在于:

  1. 进一步避免命名冲突
  2. 更适合组织中大型项目
  3. 让模块之间的归属关系更清楚

比如两个不同目录下都可以有 utils.py

  • mycompany.utils
  • mycompany.web.utils

只要包路径不同,它们就不会冲突。


七、模块搜索路径:Python 去哪里找模块?

当你写下:

1
import mymodule

Python 解释器并不是“凭空知道”这个模块在哪,它会按照一定规则去查找。

7.1 sys.path 是关键

模块搜索路径通常保存在 sys.path 中:

1
2
3
4
import sys

for path in sys.path:
print(path)

常见包含内容有:

  • 当前运行目录;
  • Python 标准库目录;
  • 第三方库安装目录(如 site-packages);
  • 你手动添加的路径。
7.2 为什么有时会报 ModuleNotFoundError

如果 Python 在搜索路径里找不到目标模块,就会报错:

1
import mymodule

可能得到:

1
ModuleNotFoundError: No module named 'mymodule'

这通常意味着:

  • 模块文件根本不存在;
  • 文件在错误目录;
  • 当前运行路径不对;
  • 包结构不完整;
  • 模块名写错了。
7.3 临时修改 sys.path

有时你会看到这样的代码:

1
2
import sys
sys.path.append('/path/to/your/modules')

这样可以临时把某个目录加入模块搜索路径。

它在调试时有用,但通常不建议作为长期方案,因为:

  • 可移植性差;
  • 运行环境一变就可能失效;
  • 容易让项目结构混乱。
7.4 更推荐的方式:组织好项目结构

与其频繁修改 sys.path,更推荐:

  • 正确组织包结构;
  • 在项目根目录运行程序;
  • 使用虚拟环境;
  • 通过标准安装方式管理依赖。
7.5 PYTHONPATH 的作用

除了运行时修改 sys.path,还可以通过环境变量 PYTHONPATH 扩展搜索路径。

不过对初学者来说,更重要的是先理解:

Python 会按照既定搜索路径查找模块,而不是扫描你电脑上的所有文件。

只要明白这一点,很多导入报错就容易定位了。


八、标准库、第三方库和自定义模块

学习模块时,最好把模块来源分清楚。

8.1 标准库模块

标准库是 Python 自带的模块,安装 Python 后就能直接用。

例如:

  • sys
  • os
  • math
  • datetime
  • json
  • pathlib

示例:

1
2
3
4
5
6
7
import os
import json
import datetime

print(os.getcwd())
print(json.dumps({'name': 'Alice', 'age': 20}, ensure_ascii=False))
print(datetime.datetime.now())
8.2 第三方模块

第三方模块不是 Python 自带的,需要额外安装。

例如:

  • requests
  • pandas
  • numpy
  • flask
  • pillow

如果未安装直接导入,就会报错。

8.3 自定义模块

你自己写的 .py 文件,也属于模块。

所以从来源来看,Python 模块大体有三类:

  1. 标准库模块
  2. 第三方模块
  3. 自定义模块

这个分类非常重要,因为它决定了模块应该如何获取和管理。


九、安装第三方模块:用 pip 管理依赖

当项目依赖外部库时,最常见的安装方式是 pip

9.1 最基础的安装命令

例如安装 requests

1
pip install requests

或者在某些环境中使用:

1
pip3 install requests

为了更明确地指定 Python 解释器,也常见这样写:

1
python -m pip install requests

这通常比直接写 pip 更稳妥。

9.2 安装后如何使用
1
2
3
4
import requests

response = requests.get('https://example.com')
print(response.status_code)
9.3 常见安装问题

1. 找不到 pip 命令

可能是:

  • Python 没正确安装;
  • pip 没加入环境变量;
  • 当前环境里命令名不是 pip

这时可以优先尝试:

1
python -m pip --version

2. 安装到了别的 Python 环境

这是初学者非常常见的问题。

例如你运行程序用的是一个 Python,安装依赖时却给另一个 Python 安装了包。结果就是:

  • pip install 看起来成功了;
  • import 还是报错。

所以推荐尽量使用:

1
python -m pip install 包名

让安装动作明确绑定到当前解释器。

3. 权限不足

在某些环境下,可能需要:

1
pip install --user requests

或者使用虚拟环境来隔离依赖。

9.4 依赖管理意识要尽早建立

安装第三方模块,不只是“能用就行”,还要逐渐建立这样的意识:

  • 这个包是项目依赖;
  • 这个依赖需要版本约束;
  • 换一台机器时,也要能重新装回来。

这也是后面学习虚拟环境和 requirements.txt 的基础。


十、虚拟环境:让不同项目的依赖互不干扰

虽然本节重点是模块,但一讲第三方模块,就很难绕开虚拟环境。

10.1 为什么需要虚拟环境?

假设你有两个项目:

  • 项目 A 依赖 requests==2.28
  • 项目 B 依赖 requests==2.31

如果都装在系统全局环境里,就可能互相影响。

虚拟环境的作用就是:

给每个项目提供一个相对独立的 Python 运行环境。

10.2 使用 venv 创建虚拟环境
1
python -m venv venv

这会创建一个名为 venv 的虚拟环境目录。

激活方式通常是:

1
source venv/bin/activate

Windows 下则可能是:

1
venv\Scripts\activate

激活后再安装依赖:

1
python -m pip install requests

这样依赖就只安装在当前项目环境里。

10.3 初学者对虚拟环境的理解重点

你不必一开始就把虚拟环境研究得很深,但至少要知道:

  • 它能隔离项目依赖;
  • 它能减少环境污染;
  • 它是实际开发里非常常见的基础设施。

十一、综合示例:组织一个小型 Python 项目

下面用一个简单例子,把模块和包的思路串起来。

项目结构:

1
2
3
4
5
6
weather_app/
├── main.py
└── app/
├── __init__.py
├── formatter.py
└── service.py

formatter.py

1
2
def format_city_name(city):
return city.strip().title()

service.py

1
2
3
4
5
6
from app.formatter import format_city_name


def get_weather_message(city):
city = format_city_name(city)
return f'{city} 今天天气晴,温度 26°C'

main.py

1
2
3
4
5
6
from app.service import get_weather_message


if __name__ == '__main__':
city = input('请输入城市名: ')
print(get_weather_message(city))

这个例子虽然很小,但已经体现了几个关键点:

  • 使用包来组织模块;
  • 使用 from ... import ... 在模块间协作;
  • if __name__ == '__main__': 作为程序入口;
  • 把“格式化逻辑”和“业务逻辑”拆到不同模块。

这就是模块化编程最核心的价值:拆分职责,保持结构清晰。


十二、学习建议:从“能导入”走向“会组织”

学这一章时,很多人容易把重点只放在 import 语法本身上。其实更重要的是建立模块化思维。

12.1 先掌握最常用的三种导入方式

优先熟悉下面三种:

1
2
3
import math
from math import sqrt
import numpy as np

这三种已经覆盖了大多数日常使用场景。

12.2 理解 __name__ == '__main__' 的意义

这是“文件可执行”和“文件可导入”之间的桥梁。

如果你真正理解了这一点,就会明白:

  • Python 文件既可以是脚本;
  • 也可以是模块;
  • 二者并不冲突。
12.3 不要把所有工具函数都塞进一个 utils.py

初学者很容易把所有杂项函数都扔进一个 utils.py。开始时可以接受,但当项目变大后,最好继续拆分,例如:

  • string_utils.py
  • file_utils.py
  • date_utils.py

这样会比“一个大杂烩模块”更容易维护。

12.4 安装第三方模块时注意解释器一致性

记住这条经验:

如果 pip install 成功了,但 import 还是报错,优先怀疑是不是装到了别的 Python 环境里。

这在真实开发里非常常见。


十三、总结

这一篇我们系统梳理了 Python 模块相关的核心内容:

  1. 模块本质上就是一个 .py 文件;
  2. 使用模块可以提升代码复用性、可维护性和组织性;
  3. import 会把模块导入当前程序,并通过命名空间访问其成员;
  4. from ... import ... 更简洁,但要注意命名冲突;
  5. if __name__ == '__main__': 用于区分“直接运行”和“被导入”;
  6. _ 开头的名字通常表示模块内部使用;
  7. 是组织多个模块的目录结构;
  8. Python 会通过 sys.path 等搜索路径查找模块;
  9. 第三方模块通常通过 pip 安装;
  10. 虚拟环境可以帮助不同项目隔离依赖。

如果说前面几篇更多是在学习“单文件里的 Python 写法”,那么这一篇开始,你已经进入“如何组织一个 Python 项目”的阶段。

下一步继续往后学时,最自然的衔接方向通常是:

  • 面向对象编程:学习类、实例、继承和多态;
  • 或者 错误处理:学习 try / except / finally、调试和测试。

这两条线都会频繁和模块化代码结合使用。

参考资料