Python 使用模块:像搭积木一样组装程序
引言:为什么没人从零开始造车
想象你要造一辆车,会怎么做?自己炼钢、自己造发动机、自己搓轮胎?当然不会——你会直接买现成的发动机、轮胎、方向盘,把它们组装起来。
写程序也一样。Python 安装完毕后,自带了几百个模块(Module)——每个模块都是一组现成的功能:处理日期的、读写文件的、操作系统的、网络通信的……你要做的不是"从零造轮子",而是"把轮子装好,开车上路"。
这一篇,我们就来学习如何使用模块,以及如何写出规范的模块文件。
一、模块到底是什么?
1.1 一个 .py 文件就是一个模块
在 Python 里,每一个 .py 文件就是一个模块。文件名(去掉 .py)就是模块名:
hello.py → 模块名:hello
utils.py → 模块名:utils
my_tool.py → 模块名:my_tool生活化理解:模块就像工具箱里的一个个格子——math 格子里放着数学工具,sys 格子里放着系统工具,hello 格子里放着你自己写的工具。
1.2 为什么要用模块?
假设你把所有代码都写在一个文件里:一万行代码挤在一起,找个函数要翻半天,改个 bug 怕碰坏别的地方。
模块化的好处:
- 按功能拆分:相关代码放一起,找东西快;
- 重复使用:写一次,到处导入,不用复制粘贴;
- 避免名字冲突:两个模块里都可以有
test()函数,互不干扰; - 团队协作:你写 A 模块,同事写 B 模块,最后拼装。
二、编写第一个模块:标准文件模板
2.1 完整的模块文件
以经典的 hello.py 为例:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
' a test module '
__author__ = 'Michael Liao'
import sys
def test():
args = sys.argv
if len(args) == 1:
print('Hello, world!')
elif len(args) == 2:
print('Hello, %s!' % args[1])
else:
print('Too many arguments!')
if __name__ == '__main__':
test()2.2 逐行拆解:模板各部分是干什么的
| 行 | 内容 | 作用 |
|---|---|---|
| 第1行 | #!/usr/bin/env python3 | 让文件在 Unix/Linux/Mac 上可直接运行(Windows 用户可忽略) |
| 第2行 | # -*- coding: utf-8 -*- | 声明文件使用 UTF-8 编码(Python 3 默认就是 UTF-8,写上更保险) |
| 第4行 | ' a test module ' | 模块的文档注释——模块代码的第一个字符串会被当作模块说明 |
| 第6行 | __author__ = '...' | 作者署名,开源项目的惯例 |
| 第8行 | import sys | 导入内置的 sys 模块 |
| 后面 | 真正的代码 | 函数、变量、逻辑 |
注意:这些都是约定俗成的规范,不是语法强制。全删掉程序照样跑,但写上它们,别人一看就知道你是"专业选手"。
2.3 模块文档注释有什么用?
模块的第一个字符串会成为模块的文档,可以通过 __doc__ 访问:
>>> import hello
>>> hello.__doc__
' a test module '别人用 help(hello) 查看你的模块时,看到的第一段说明就是它。
三、导入模块:import 的几种姿势
3.1 基本导入:import 模块名
import sys执行这行代码后,你得到了一个变量 sys,它指向整个 sys 模块。通过 sys.xxx 可以访问模块里的所有功能:
>>> sys.argv # 命令行参数列表
>>> sys.version # Python 版本信息
>>> sys.platform # 操作系统平台生活化理解:import sys 就像把整个工具箱搬回家,用的时候喊"工具箱里的螺丝刀"(sys.argv)。
3.2 导入并起别名:import ... as ...
模块名太长?起个短一点的别名:
import sys as system
print(system.platform)实际项目中最常见的例子:
import numpy as np # 科学计算库的通用别名
import pandas as pd # 数据分析库的通用别名
import matplotlib.pyplot as plt3.3 只导入需要的部分:from ... import ...
from sys import argv
print(argv) # 直接用 argv,不用写 sys.argv还可以一次导入多个:
from sys import argv, platform, version3.4 危险操作:from ... import *
from sys import * # 把 sys 里所有公开名字都倒进当前命名空间强烈不推荐! 原因:
- 你不知道到底导入了什么,可能覆盖掉你自己的同名变量;
- 代码可读性变差——看到一个函数,不知道它来自哪个模块;
- IDE 和代码检查工具无法有效提示。
对比记忆:
| 写法 | 调用方式 | 推荐程度 |
|---|---|---|
import sys | sys.argv | ⭐⭐⭐ 最推荐,来源清晰 |
import sys as s | s.argv | ⭐⭐ 模块名太长时用 |
from sys import argv | argv | ⭐⭐ 少量明确的名字可用 |
from sys import * | argv | ❌ 不要用 |
四、深入 sys.argv:命令行参数
4.1 argv 是什么?
sys.argv 是一个 list,存储了命令行传入的所有参数:
python3 hello.py
# argv = ['hello.py']
python3 hello.py Michael
# argv = ['hello.py', 'Michael']
python3 hello.py Michael 25
# argv = ['hello.py', 'Michael', '25']关键规则:
argv至少有一个元素——第一个永远是.py文件自己的名字;- 所有参数都是字符串,数字要手动转(
int(argv[2]))。
4.2 用 argv 做一个命令行小工具
# greet.py
import sys
def main():
if len(sys.argv) < 2:
print('用法:python greet.py <名字>')
return
name = sys.argv[1]
print(f'你好,{name}!')
if __name__ == '__main__':
main()python greet.py 小明
# 你好,小明!
python greet.py
# 用法:python greet.py <名字>应用场景:写脚本工具时,让用户通过命令行传入文件名、参数等,比如 python backup.py 数据库名。
五、神奇的 if __name__ == '__main__'
5.1 两行代码的两种命运
if __name__ == '__main__':
test()这两行代码解决了一个经典矛盾:
- 直接运行
hello.py时:希望执行test(); - 被别人导入
import hello时:不希望自动执行test()。
5.2 原理:__name__ 这个特殊变量
Python 每个模块都有个内置变量 __name__:
- 当模块被直接运行时,
__name__的值是'__main__'; - 当模块被导入时,
__name__的值是模块名(比如'hello')。
验证一下:
# hello.py
print(f'__name__ 的值是:{__name__}')
if __name__ == '__main__':
print('我是被直接运行的!')# 直接运行
python hello.py
# __name__ 的值是:__main__
# 我是被直接运行的!
# 交互环境中导入
>>> import hello
# __name__ 的值是:hello
# (没有打印"我是被直接运行的")5.3 生活化理解
__name__ 像一个"身份识别器":
- 直接运行 = 你是主角(
__main__),登台表演; - 被导入 = 你是配角(
hello),在旁边待命,等主角喊你。
5.4 最常见的用途:写测试代码
# my_math.py
def add(a, b):
return a + b
def multiply(a, b):
return a * b
# 以下代码只在直接运行时执行,导入时跳过
if __name__ == '__main__':
# 自我测试
assert add(1, 2) == 3
assert multiply(3, 4) == 12
print('所有测试通过!')直接运行 python my_math.py 会跑测试;别人 import my_math 只会拿到函数,不会触发测试。
六、作用域:public 和 private 的约定
6.1 三种命名风格的含义
模块里的函数和变量,名字写法不同,含义就不同:
| 命名风格 | 例子 | 含义 | 能否外部访问 |
|---|---|---|---|
| 普通名字 | abc, x123, PI | 公开(public),欢迎外部使用 | ✅ 可以 |
| 前后双下划线 | __name__, __author__ | 特殊变量,Python 系统用 | ✅ 可以,但别自己起这种名 |
| 单/双下划线开头 | _abc, __abc | 私有(private),仅限模块内部 | ⚠️ 技术上能,习惯上别 |
6.2 重点:Python 的 private 是"君子协定"
def _private_1(name):
return 'Hello, %s' % name
def _private_2(name):
return 'Hi, %s' % name
def greeting(name):
if len(name) > 3:
return _private_1(name)
else:
return _private_2(name)这里 greeting 是公开的,_private_1 和 _private_2 是私有的。
注意措辞:我们说 private "不应该"被外部引用,而不是"不能"被引用。Python 没有任何机制能真正阻止你访问 _private_1——它靠的是编程习惯和团队默契。
生活化理解:下划线前缀像酒店房间门上的"请勿打扰"牌子——服务员(外部代码)看到牌子就不该推门进去,但门并没有上锁,硬闯也能进。
6.3 为什么要区分 public 和 private?
封装的核心思想:外部只关心"能干什么",不关心"怎么实现"。
# 外部调用者只需要知道:
greeting('Michael') # 输出 Hello, Michael
# 不需要知道内部是 _private_1 还是 _private_2 干的活好处:
- 简化使用:用户只需记住一个
greeting,不用记一堆内部函数; - 保护内部:内部实现随时可以改(比如把
_private_1改名、合并),只要greeting的对外行为不变,外部代码就不受影响; - 减少误用:防止别人直接调用半成品函数,产生奇怪的结果。
6.4 实践建议
外部不需要引用的,全部定义成 private;只有外部需要的,才定义为 public。
写模块时,先问自己:"这个函数/变量是给外面用的,还是我自己用的?"——自己用的,前面加 _。
七、常见误区与避坑指南
7.1 误区一:模块名和标准库重名
# 千万别把自己的文件命名为这些:
sys.py
os.py
json.py
email.py如果你建了个 json.py,然后 import json,Python 会优先导入你自己的文件,而不是标准库的 json——导致各种莫名其妙的错误。
排查技巧:如果导入报错 AttributeError: module 'json' has no attribute 'loads',先检查当前目录有没有同名文件。
7.2 误区二:以为 import 只是"复制代码"
# a.py
x = 1
# b.py
import a
a.x = 999
# c.py
import a
print(a.x) # 输出 999!模块被导入后,所有导入它的地方共享同一个模块对象。改了一处,处处生效。
生活化理解:模块像小区的公共花园——谁家都能去,但如果有人在花园里挖坑,全小区的人都看到那个坑。
7.3 误区三:循环导入
# a.py
import b
def func_a():
b.func_b()
# b.py
import a
def func_b():
a.func_a()a 导入 b,b 又导入 a——像两只猫互相追尾巴,Python 会报 ImportError 或得到半成品的模块对象。
解决办法:
- 把公共部分抽到第三个模块;
- 把
import语句放到函数内部(延迟导入); - 重新设计模块划分,避免互相依赖。
7.4 误区四:忘记 if __name__ == '__main__'
# utils.py
import time
print('正在初始化...') # 模块级代码
time.sleep(5)
def useful_func():
pass别人 import utils 时,会白白等 5 秒——模块级的代码在导入时就会执行!
原则:模块顶部只写定义(函数、类、常量),执行性代码全部包进 if __name__ == '__main__': 或函数里。
7.5 误区五:乱用 from ... import * 导致名字污染
from math import *
from numpy import *
# 现在 sin 是 math.sin 还是 numpy.sin?天知道!两个模块都有 sin 函数,后导入的会覆盖先导入的,调试时你会怀疑人生。
修正:要么 import math / import numpy as np,要么明确写出要导入的名字。
八、实战练习
练习 1:写一个命令行计算器
创建 calc.py,支持命令行调用:
python calc.py add 3 5 # 输出 8
python calc.py multiply 4 6 # 输出 24要求:使用 sys.argv 解析参数,参数不足时打印用法提示。
参考答案
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
' a simple command line calculator '
__author__ = 'Your Name'
import sys
def add(a, b):
return a + b
def multiply(a, b):
return a * b
def main():
if len(sys.argv) != 4:
print('用法:python calc.py <add|multiply> <数字1> <数字2>')
return
op = sys.argv[1]
a = int(sys.argv[2])
b = int(sys.argv[3])
if op == 'add':
print(add(a, b))
elif op == 'multiply':
print(multiply(a, b))
else:
print(f'不支持的操作:{op}')
if __name__ == '__main__':
main()练习 2:封装一个打招呼模块
创建 greetings.py:
- 公开函数
say_hello(name):根据名字长度返回不同问候语 - 内部逻辑用
_formal_hello和_casual_hello两个私有函数实现 - 直接运行时执行简单测试
参考答案
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
' greeting module '
__author__ = 'Your Name'
def _formal_hello(name):
return f'您好,{name}女士/先生!'
def _casual_hello(name):
return f'嗨,{name}!'
def say_hello(name):
if len(name) > 2:
return _formal_hello(name)
else:
return _casual_hello(name)
if __name__ == '__main__':
# 自测试
print(say_hello('欧阳娜娜')) # 正式版
print(say_hello('小明')) # 随意版九、小结
- 模块就是一个
.py文件,Python 内置几百个模块开箱即用,自己也可以写模块; - 标准模板:shebang 行、编码声明、文档注释、
__author__,写上是好习惯; - 导入方式:
import sys最推荐,from sys import argv次之,from sys import *禁用; sys.argv获取命令行参数,第一个元素永远是脚本名,所有参数都是字符串;if __name__ == '__main__':让模块"直接运行时干活,被导入时安静",常用于写自测试;- public / private 约定:普通名字公开,
_开头私有(君子协定),__xxx__是特殊变量别乱用; - 避坑要点:别和标准库重名、模块是共享的、避免循环导入、执行代码包进
__main__。
模块是 Python 组织代码的基本单元——学会了使用模块,你就从"写脚本"升级到了"搭工程"。下一篇我们将学习如何安装第三方模块,打开 Python 生态的宝库。