Skip to content

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 怕碰坏别的地方。

模块化的好处:

  1. 按功能拆分:相关代码放一起,找东西快;
  2. 重复使用:写一次,到处导入,不用复制粘贴;
  3. 避免名字冲突:两个模块里都可以有 test() 函数,互不干扰;
  4. 团队协作:你写 A 模块,同事写 B 模块,最后拼装。

二、编写第一个模块:标准文件模板

2.1 完整的模块文件

以经典的 hello.py 为例:

python
#!/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__ 访问:

python
>>> import hello
>>> hello.__doc__
' a test module '

别人用 help(hello) 查看你的模块时,看到的第一段说明就是它。


三、导入模块:import 的几种姿势

3.1 基本导入:import 模块名

python
import sys

执行这行代码后,你得到了一个变量 sys,它指向整个 sys 模块。通过 sys.xxx 可以访问模块里的所有功能:

python
>>> sys.argv        # 命令行参数列表
>>> sys.version     # Python 版本信息
>>> sys.platform    # 操作系统平台

生活化理解import sys 就像把整个工具箱搬回家,用的时候喊"工具箱里的螺丝刀"(sys.argv)。

3.2 导入并起别名:import ... as ...

模块名太长?起个短一点的别名:

python
import sys as system
print(system.platform)

实际项目中最常见的例子:

python
import numpy as np          # 科学计算库的通用别名
import pandas as pd         # 数据分析库的通用别名
import matplotlib.pyplot as plt

3.3 只导入需要的部分:from ... import ...

python
from sys import argv

print(argv)   # 直接用 argv,不用写 sys.argv

还可以一次导入多个:

python
from sys import argv, platform, version

3.4 危险操作:from ... import *

python
from sys import *    # 把 sys 里所有公开名字都倒进当前命名空间

强烈不推荐! 原因:

  1. 你不知道到底导入了什么,可能覆盖掉你自己的同名变量
  2. 代码可读性变差——看到一个函数,不知道它来自哪个模块;
  3. IDE 和代码检查工具无法有效提示。

对比记忆

写法调用方式推荐程度
import syssys.argv⭐⭐⭐ 最推荐,来源清晰
import sys as ss.argv⭐⭐ 模块名太长时用
from sys import argvargv⭐⭐ 少量明确的名字可用
from sys import *argv❌ 不要用

四、深入 sys.argv:命令行参数

4.1 argv 是什么?

sys.argv 是一个 list,存储了命令行传入的所有参数:

bash
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 做一个命令行小工具

python
# 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()
bash
python greet.py 小明
# 你好,小明!

python greet.py
# 用法:python greet.py <名字>

应用场景:写脚本工具时,让用户通过命令行传入文件名、参数等,比如 python backup.py 数据库名


五、神奇的 if __name__ == '__main__'

5.1 两行代码的两种命运

python
if __name__ == '__main__':
    test()

这两行代码解决了一个经典矛盾:

  • 直接运行 hello.py 时:希望执行 test()
  • 被别人导入 import hello 时:不希望自动执行 test()

5.2 原理:__name__ 这个特殊变量

Python 每个模块都有个内置变量 __name__

  • 当模块被直接运行时,__name__ 的值是 '__main__'
  • 当模块被导入时,__name__ 的值是模块名(比如 'hello')。

验证一下:

python
# hello.py
print(f'__name__ 的值是:{__name__}')

if __name__ == '__main__':
    print('我是被直接运行的!')
bash
# 直接运行
python hello.py
# __name__ 的值是:__main__
# 我是被直接运行的!

# 交互环境中导入
>>> import hello
# __name__ 的值是:hello
# (没有打印"我是被直接运行的")

5.3 生活化理解

__name__ 像一个"身份识别器":

  • 直接运行 = 你是主角(__main__),登台表演;
  • 被导入 = 你是配角(hello),在旁边待命,等主角喊你。

5.4 最常见的用途:写测试代码

python
# 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 是"君子协定"

python
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?

封装的核心思想:外部只关心"能干什么",不关心"怎么实现"

python
# 外部调用者只需要知道:
greeting('Michael')   # 输出 Hello, Michael

# 不需要知道内部是 _private_1 还是 _private_2 干的活

好处:

  1. 简化使用:用户只需记住一个 greeting,不用记一堆内部函数;
  2. 保护内部:内部实现随时可以改(比如把 _private_1 改名、合并),只要 greeting 的对外行为不变,外部代码就不受影响;
  3. 减少误用:防止别人直接调用半成品函数,产生奇怪的结果。

6.4 实践建议

外部不需要引用的,全部定义成 private;只有外部需要的,才定义为 public。

写模块时,先问自己:"这个函数/变量是给外面用的,还是我自己用的?"——自己用的,前面加 _


七、常见误区与避坑指南

7.1 误区一:模块名和标准库重名

python
# 千万别把自己的文件命名为这些:
sys.py
os.py
json.py
email.py

如果你建了个 json.py,然后 import json,Python 会优先导入你自己的文件,而不是标准库的 json——导致各种莫名其妙的错误。

排查技巧:如果导入报错 AttributeError: module 'json' has no attribute 'loads',先检查当前目录有没有同名文件。

7.2 误区二:以为 import 只是"复制代码"

python
# a.py
x = 1

# b.py
import a
a.x = 999

# c.py
import a
print(a.x)   # 输出 999!

模块被导入后,所有导入它的地方共享同一个模块对象。改了一处,处处生效。

生活化理解:模块像小区的公共花园——谁家都能去,但如果有人在花园里挖坑,全小区的人都看到那个坑。

7.3 误区三:循环导入

python
# 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 或得到半成品的模块对象。

解决办法

  1. 把公共部分抽到第三个模块;
  2. import 语句放到函数内部(延迟导入);
  3. 重新设计模块划分,避免互相依赖。

7.4 误区四:忘记 if __name__ == '__main__'

python
# utils.py
import time

print('正在初始化...')    # 模块级代码
time.sleep(5)

def useful_func():
    pass

别人 import utils 时,会白白等 5 秒——模块级的代码在导入时就会执行!

原则:模块顶部只写定义(函数、类、常量),执行性代码全部包进 if __name__ == '__main__': 或函数里。

7.5 误区五:乱用 from ... import * 导致名字污染

python
from math import *
from numpy import *

# 现在 sin 是 math.sin 还是 numpy.sin?天知道!

两个模块都有 sin 函数,后导入的会覆盖先导入的,调试时你会怀疑人生。

修正:要么 import math / import numpy as np,要么明确写出要导入的名字。


八、实战练习

练习 1:写一个命令行计算器

创建 calc.py,支持命令行调用:

bash
python calc.py add 3 5       # 输出 8
python calc.py multiply 4 6  # 输出 24

要求:使用 sys.argv 解析参数,参数不足时打印用法提示。

参考答案
python
#!/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 两个私有函数实现
  • 直接运行时执行简单测试
参考答案
python
#!/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('小明'))       # 随意版

九、小结

  1. 模块就是一个 .py 文件,Python 内置几百个模块开箱即用,自己也可以写模块;
  2. 标准模板:shebang 行、编码声明、文档注释、__author__,写上是好习惯;
  3. 导入方式import sys 最推荐,from sys import argv 次之,from sys import * 禁用;
  4. sys.argv 获取命令行参数,第一个元素永远是脚本名,所有参数都是字符串;
  5. if __name__ == '__main__':让模块"直接运行时干活,被导入时安静",常用于写自测试;
  6. public / private 约定:普通名字公开,_ 开头私有(君子协定),__xxx__ 是特殊变量别乱用;
  7. 避坑要点:别和标准库重名、模块是共享的、避免循环导入、执行代码包进 __main__

模块是 Python 组织代码的基本单元——学会了使用模块,你就从"写脚本"升级到了"搭工程"。下一篇我们将学习如何安装第三方模块,打开 Python 生态的宝库。