跳到主要内容

模块

随着代码量增长,把所有逻辑塞进一个 .py 文件会变得难以维护。Python 通过模块(module)机制,将代码按文件拆分、组织、复用。一个 .py 文件就是一个模块,文件名即模块名(去掉 .py 后缀)。模块让代码具备了复用、隔离、命名空间三大能力——这是从中型项目走向大型项目的第一步。

import 语句

最基础的导入方式是 import 模块名。导入后,通过 模块名.属性 访问其中的函数、类、变量:

# 标准库 math 模块
import math

print(math.pi) # 输出:3.141592653589793
print(math.sqrt(16)) # 输出:4.0
print(math.factorial(5)) # 输出:120

导入时会完整执行模块顶层代码一次,后续再次 import 同一模块直接命中缓存(见 sys.modules),不会重复执行。

import math
import math # 第二次不会重新执行 math 模块

import sys
print("math" in sys.modules) # 输出:True

from...import

如果只需要模块中的部分内容,使用 from 模块名 import 名称,可以直接用名称,免去模块名前缀:

from math import pi, sqrt, factorial

print(pi) # 输出:3.141592653589793
print(sqrt(16)) # 输出:4.0
print(factorial(5)) # 输出:120

:::warning 谨慎使用 from ... import * from math import * 会把模块所有公开名称导入当前命名空间,容易覆盖同名变量且难以追溯来源,PEP 8 明确不推荐在生产代码中使用。

from math import *
from os import * # 危险:os.open 会覆盖内置 open

# 不要这样写!

:::

import as 别名

当模块名较长或与本地变量冲突时,使用 as 起别名:

import numpy as np # 第三方库常用别名(约定俗成)
import pandas as pd
import matplotlib.pyplot as plt

# 也适用于 from...import
from math import factorial as fact

print(fact(5)) # 输出:120

:::tip 主流库的别名约定 数据科学生态有一套事实上的命名约定,遵守它们能让代码更易读:

  • import numpy as np
  • import pandas as pd
  • import matplotlib.pyplot as plt
  • import seaborn as sns
  • import tensorflow as tf :::

name == 'main'

每个模块都有一个特殊属性 __name__

  • 当模块被直接运行时,__name__ 等于 "__main__"
  • 当模块被导入时,__name__ 等于模块名

这一机制常用于在文件中编写"既能作为脚本执行,又能作为模块导入"的代码:

# greeter.py
def greet(name: str) -> str:
return f"你好,{name}!"


def main() -> None:
name = input("请输入姓名:")
print(greet(name))


if __name__ == "__main__":
# 只有直接运行 greeter.py 才会执行
# 被 import 时不执行,避免副作用
main()
python greeter.py
# 请输入姓名:Alice
# 你好,Alice!
# 在另一个文件中
import greeter
print(greeter.greet("Bob")) # 输出:你好,Bob!
# 不会触发 input(),因为 __name__ != "__main__"

:::info 何时使用 name 守卫

  • 模块既作为脚本运行,又作为库被导入
  • 包含示例代码自测代码CLI 入口
  • 避免导入时产生副作用(如启动服务、写入文件、连接数据库) :::

all 控制导出

__all__ 是一个列表,明确声明模块的公开 API。它影响两件事:

  1. from module import * 时只会导入 __all__ 中的名称
  2. 文档工具(如 Sphinx)据此生成 API 文档
# string_utils.py
__all__ = ["capitalize_words", "reverse_string"]


def capitalize_words(text: str) -> str:
"""将每个单词首字母大写。"""
return " ".join(w.capitalize() for w in text.split())


def reverse_string(text: str) -> str:
"""反转字符串。"""
return text[::-1]


def _internal_helper(text: str) -> str:
"""内部辅助函数,不希望被外部使用。"""
return text.strip().lower()


# 使用示例
if __name__ == "__main__":
print(capitalize_words("hello world")) # 输出:Hello World
print(reverse_string("Python")) # 输出:nohtyP
from string_utils import *

print(capitalize_words("hi there")) # 输出:Hi There
print(reverse_string("abc")) # 输出:cba
# print(_internal_helper("X")) # NameError: 未导出

:::tip 单下划线约定 即便没有 __all__,以单下划线开头的名称(如 _internal_helper)也被视为"模块私有",from module import * 不会导入它们。这是约定,不是强制——你仍可以显式 from string_utils import _internal_helper。 :::

标准库模块示例

Python 标准库提供了 200+ 个模块,覆盖了日常开发的绝大部分需求。

os — 操作系统接口

import os

# 当前工作目录
print(os.getcwd()) # 输出示例:/home/user/project

# 环境变量
print(os.environ.get("HOME", "/home/user"))

# 拼接路径(跨平台安全)
config_path = os.path.join("config", "settings.json")
print(config_path) # Linux: config/settings.json Windows: config\settings.json

# 列出目录内容
for entry in os.listdir("."):
print(entry)

pathlib — 现代路径操作(推荐)

from pathlib import Path

# 比 os.path 更优雅
home = Path.home()
print(home) # 输出示例:/home/user

config = home / "config" / "settings.json"
print(config) # 输出示例:/home/user/config/settings.json

# 递归查找所有 Python 文件
for py_file in Path(".").rglob("*.py"):
print(py_file)

datetime — 日期与时间

from datetime import datetime, date, timedelta

now = datetime.now()
print(now.strftime("%Y-%m-%d %H:%M:%S")) # 输出示例:2026-07-10 14:30:00

today = date.today()
yesterday = today - timedelta(days=1)
print(yesterday) # 输出示例:2026-07-09

collections — 高级容器

from collections import Counter, defaultdict, deque

# 计数
words = "the quick brown fox jumps over the lazy dog the".split()
counter = Counter(words)
print(counter.most_common(2))
# 输出:[('the', 3), ('quick', 1)]

# 默认值字典
word_len = defaultdict(list)
for w in words:
word_len[len(w)].append(w)
print(word_len[3]) # 输出:['the', 'fox', 'the', 'dog', 'the']

# 双端队列
queue = deque([1, 2, 3], maxlen=5)
queue.appendleft(0)
queue.append(4)
print(queue) # 输出:deque([0, 1, 2, 3, 4], maxlen=5)

itertools — 迭代器工具

from itertools import chain, combinations, islice

# 拼接多个迭代器
for item in chain([1, 2], ["a", "b"], (True, False)):
print(item, end=" ")
# 输出:1 2 a b True False

# 所有组合
for combo in combinations("ABC", 2):
print(combo)
# 输出:('A', 'B') / ('A', 'C') / ('B', 'C')

# 切片迭代器
first_three = list(islice(range(100), 3))
print(first_three) # 输出:[0, 1, 2]

模块搜索路径 sys.path

当执行 import foo 时,Python 解释器按以下顺序查找 foo 模块:

  1. 内置模块(如 sysbuiltins
  2. 当前脚本所在目录(或交互式解释器的当前目录)
  3. PYTHONPATH 环境变量中的目录
  4. 标准库目录
  5. 第三方包目录(site-packages

这些路径存储在 sys.path 列表中:

import sys

for i, path in enumerate(sys.path):
print(f"{i}: {path}")

输出示例:

0: /home/user/project
1: /usr/lib/python312.zip
2: /usr/lib/python3.12
3: /usr/lib/python3.12/lib-dynload
4: /home/user/.venv/lib/python3.12/site-packages

临时添加搜索路径

import sys
from pathlib import Path

# 在程序中动态添加(不推荐作为长期方案)
sys.path.insert(0, str(Path(__file__).parent / "libs"))

import my_custom_module # 现在可以找到 libs/my_custom_module.py

:::warning 不要滥用 sys.path 修改 直接修改 sys.path 会让项目难以维护和部署。更好的方式:

  • 把代码组织成(下一节会讲)
  • pip install -e . 以可编辑模式安装项目
  • 使用 pyproject.toml 声明项目结构 :::

模块缓存 sys.modules

Python 维护一个全局字典 sys.modules,记录所有已加载的模块。再次 import 同一模块时直接从这里取,避免重复执行:

import sys
import math

print(id(sys.modules["math"])) # 模块对象的内存地址

# 再次 import 不会重新加载
import math
print(id(math)) # 与上面相同

# 强制重新加载(开发调试用)
import importlib
importlib.reload(math) # 注意:不会更新已 from...import 的引用

pycache 与字节码

首次导入模块时,Python 会将源码编译为字节码(.pyc 文件)缓存到 __pycache__ 目录,下次导入若源码未修改则直接加载字节码,加快启动速度。

project/
├── greeter.py
└── __pycache__/
└── greeter.cpython-312.pyc

文件名格式:模块名.cpython-版本号.pyc

# 查看字节码
import dis

def add(a: int, b: int) -> int:
return a + b

dis.dis(add)

输出示例:

1 RESUME 0
2 LOAD_FAST 0 (a)
LOAD_FAST 1 (b)
BINARY_OP 0 (+)
RETURN_VALUE

:::note 关于 pycache

  • 可以随时删除 __pycache__ 目录,Python 会在下次运行时重新生成
  • .gitignore 中加入 __pycache__/*.pyc
  • 发布到生产环境时通常不需要 .pyc,部署时会重新生成
  • 设置环境变量 PYTHONDONTWRITEBYTECODE=1 可禁止生成 :::

实战:自定义工具模块

下面我们创建一个完整的字符串处理工具模块,包含导出控制、文档字符串、__main__ 自测:

# string_kit.py
"""字符串处理工具箱。

提供常用字符串操作,遵循 Python 3.12+ 类型注解规范。
"""

from __future__ import annotations

__version__ = "1.0.0"
__author__ = "study-python"

__all__ = [
"slugify",
"truncate",
"count_words",
"camel_to_snake",
]


def slugify(text: str, separator: str = "-") -> str:
"""将文本转为 URL 友好的 slug。

>>> slugify("Hello World!")
'hello-world'
"""
import re
text = text.lower().strip()
text = re.sub(r"[^\w\s-]", "", text)
text = re.sub(r"[\s_-]+", separator, text)
return text.strip(separator)


def truncate(text: str, max_len: int, suffix: str = "...") -> str:
"""截断字符串到指定长度,超出部分用 suffix 替代。

>>> truncate("Hello World", 8)
'Hello...'
"""
if len(text) <= max_len:
return text
return text[: max_len - len(suffix)] + suffix


def count_words(text: str) -> dict[str, int]:
"""统计每个单词出现次数。

>>> count_words("a a b")
{'a': 2, 'b': 1}
"""
from collections import Counter
return dict(Counter(text.split()))


def camel_to_snake(name: str) -> str:
"""驼峰命名转蛇形命名。

>>> camel_to_snake("camelCaseName")
'camel_case_name'
"""
import re
s1 = re.sub(r"(.)([A-Z][a-z]+)", r"\1_\2", name)
return re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", s1).lower()


def _internal_debug(text: str) -> str:
"""内部调试函数,不会通过 from ... import * 导出。"""
return repr(text)


if __name__ == "__main__":
# 模块自测:python -m string_kit 或 python string_kit.py
import doctest
failures, tests = doctest.testmod(verbose=True)
if failures == 0:
print(f"\n✓ 所有 {tests} 个测试通过")

运行自测:

python string_kit.py

输出示例:

Trying:
slugify("Hello World!")
Expecting:
'hello-world'
ok
Trying:
truncate("Hello World", 8)
Expecting:
'Hello...'
ok
...
2 items passed all tests:
4 tests in __main__
4 tests in 2 items.
4 passed and 0 failed.
Test passed.
✓ 所有 4 个测试通过

在其他文件中使用:

# main.py
import string_kit
from string_kit import slugify, camel_to_snake

print(string_kit.__version__) # 输出:1.0.0
print(slugify("Hello World! 你好")) # 输出:hello-world-你好
print(camel_to_snake("getUserById")) # 输出:get_user_by_id

:::tip 模块自测三件套

  • 文档字符串中写 >>> 测试用例
  • if __name__ == "__main__" 块中调用 doctest.testmod()
  • python -m 模块名 形式运行(推荐,等价于直接运行) :::

小结

  • 一个 .py 文件就是一个模块,文件名即模块名
  • import module 引入完整模块,from module import name 引入部分名称,as 起别名
  • __name__ == "__main__" 让模块既能直接运行,又能被导入而不产生副作用
  • __all__ 显式声明公开 API,控制 from module import * 行为
  • 标准库是 Python 的"宝藏",熟练使用 ospathlibdatetimecollectionsitertools 能极大提升效率
  • sys.path 决定模块查找顺序,优先使用包结构和 pip install -e . 而非手动修改
  • __pycache__ 缓存字节码加快启动,可放心删除

下一节将介绍包(package)——把多个模块组织成层级化的目录结构,进一步管理大型项目的代码。