☰
五分钟学会Python 模块和包、文件
2026/10/17 9:23:29 网站建设 项目流程

前言


这个标题把"模块""包""文件"三个词并列在一起,容易让人以为它们是同一件事的三个叫法。实际情况是:模块(module)和包(package)是导入系统里的两个概念,文件(file)是操作系统里的东西,它们只在"模块通常对应一个.py文件"这一点上沾边。一个包是一整个目录,它不是一个文件;而open()打开的文本文件跟导入系统毫无关系。分不清这三者,就会写出"为什么import找不到我刚建的文件"这类问题。


另一个常见误解是"包里必须有一个__init__.py,否则不叫包"。在 Python 3.3 之后这已经不成立了:PEP 420 引入了隐式命名空间包(namespace package),没有__init__.py的目录也可以被导入。当然,在需要执行包初始化代码时,__init__.py仍然是首选写法。


本文用最短的篇幅讲清导入查找规则、__name__的作用、包的目录结构,以及模块与文件在真正读写文件时该怎么配合。


一、模块:一个可导入的命名空间


在 Python 里,任何一个.py文件都是一个模块。import math会去查找math.py或内置模块,执行其中的顶层代码,并把结果绑定到当前命名空间的一个名字上。第一次导入会执行模块的顶层语句,之后再导入直接复用缓存(sys.modules),不会重复执行。


import的查找顺序大致是:内置模块 →sys.path里的目录。而sys.path的开头通常是当前脚本所在目录(或交互式解释器的当前工作目录),其后是环境变量PYTHONPATH里的条目,最后是安装目录下的site-packages。这就是为什么"把a.py和b.py放在同一个文件夹、在a.py里import b"能直接跑通——不是魔法,是脚本目录被放进了sys.path。


# 适用于 Python 3.8+
# 文件名 myutil.py
def greet(name):
return f"hello, {name}"


if __name__ == "__main__":
# 只有直接运行本文件时执行,被 import 时不执行
print(greet("world"))

__name__是模块的一个属性:当文件被直接运行时它是字符串"__main__",被导入时它是模块名(如"myutil")。所以那句判断既是"入口开关",也是"防止导入时执行测试代码"的护栏。


二、包:带层级的一整个目录


包是模块的容器,用点号分层。myapp.utils.text意味着目录嵌套myapp/utils/text.py。传统写法要求每层目录有__init__.py;PEP 420(Python 3.3 起)之后可以省略,解释器会把这样的目录当作命名空间包。


myapp/
├── __init__.py # 可选(3.3+),可为空
├── main.py
└── utils/
├── __init__.py
└── text.py

包内部的模块要用相对导入互相引用,比如在myapp/main.py里写from .utils import text。相对导入的"点"表示当前包层级,.是同级,..是上一级。注意相对导入只在包内有效:直接python myapp/main.py运行会因为没有父包上下文而报ImportError: attempted relative import with no known parent package。正确的运行方式是python -m myapp.main,用-m让解释器把myapp当作包来加载。




写法含义何时用



import a.b.c绑定名字a,需用a.b.c.x想保留完整层级

from a.b import c直接把c绑到当前命名空间最常用,代码更短

from a.b.c import func只导入一个函数明确依赖,但注意名字冲突

from . import c包内相对导入同级模块包内部模块互相引用

import a.b.c as abc起别名名字太长或避免冲突



三、文件:跟导入系统是两回事


"文件"指的是真实的磁盘 I/O。读写的入口是内置函数open(file, mode='r', encoding=...)。这里有个跨平台的关键点:不显式传encoding时,open()用的是平台默认编码——官方文档的原话是"默认编码依赖平台,即locale.getencoding()返回的值"。在中文 Windows 上这通常是cp936(GBK 系),在 Linux/macOS 上通常是 UTF-8。同一份 UTF-8 文件在 Windows 上按cp936去解,就会乱码或抛UnicodeDecodeError。


因此现行做法是永远显式写encoding="utf-8"。Python 3.10 起可以用-X warn_default_encoding或sys.flags.warn_default_encoding让这种情况发出EncodingWarning(PEP 597),这是一种排查手段;而 PEP 686 已决定从Python 3.15 起把 UTF-8 模式设为默认。在那之前,显式写编码是最稳妥的写法。


# 适用于 Python 3.8+
from pathlib import Path

p = Path("notes.txt")
p.write_text("第一行\n第二行\n", encoding="utf-8") # 写
content = p.read_text(encoding="utf-8") # 读
print(content, end="")

with p.open("a", encoding="utf-8") as f: # 追加
f.write("第三行\n")

write_text/read_text是pathlib提供的便捷方法,签名是read_text(encoding=None, errors=None, newline=None)与write_text(data, encoding=None, errors=None, newline=None),底层就是open(),所以同样需要显式传编码。


四、把三者串起来:导入自己的文件处理模块


真正写项目时,你往往会把文件处理逻辑放进一个模块,再由主程序导入。一个可运行的最小示例:


# 适用于 Python 3.8+
# 文件名 filetools.py
from pathlib import Path


def count_lines(path):
"""统计文本文件行数,显式指定编码避免跨平台乱码。"""
p = Path(path)
with p.open("r", encoding="utf-8") as f:
return sum(1 for _ in f)


if __name__ == "__main__":
print(count_lines("filetools.py"))

在另一个文件run.py里(与filetools.py同目录):


# 适用于 Python 3.8+
import filetools

print(filetools.count_lines("filetools.py"))

因为run.py所在目录在sys.path里,import filetools能直接找到同目录的模块。这也解释了为什么换到别的目录运行会报ModuleNotFoundError——sys.path的第一项变了。


常见坑点



  1. ❌ 用文件名当模块名,命名出json.py、random.py


✅ 这会把标准库模块"顶替"掉,import json导入的是你自己的文件,引发一连串莫名其妙的错误;换名字



  1. ❌ 直接python myapp/main.py却在main.py里写了from . import x


✅ 相对导入需要包上下文,改用python -m myapp.main运行



  1. ❌open("data.txt")不写encoding,在 Windows 上读 UTF-8 文件


✅ 显式写成open("data.txt", encoding="utf-8"),因为默认编码是平台相关的



  1. ❌ 以为包必须带__init__.py,没有就不叫包


✅ PEP 420 起(Python 3.3+)目录可作隐式命名空间包;需要初始化代码时才加__init__.py



  1. ❌ 在模块顶层写耗时逻辑或print


✅ 包在if __name__ == "__main__":里,否则每次import都会执行一遍



  1. ❌ 两个模块互相import,形成循环导入


✅ 把公共部分抽到第三个模块,或把导入移到函数内部(局部导入)推迟到调用时



  1. ❌from mymodule import *图省事


✅ 这会污染命名空间、覆盖同名变量;显式列出需要的名字,或用__all__控制导出



  1. ❌ 把"包"和"文件夹"混为一谈,随手把数据文件也塞进包目录


✅ 包是给导入系统用的目录结构,数据文件应放在明确的资源目录并用路径读取


总结




概念是什么关键点



模块 module一个.py文件顶层代码只执行一次,结果缓存在sys.modules

包 package一个目录3.3+ 可无__init__.py;包内用相对导入

文件 file磁盘上的字节open()不写encoding用平台默认编码,务必显式指定



把"模块是导入单元、包是目录、文件是 I/O 对象"这三条分清,"导入找不到模块"和"文件读出来乱码"这两类高频问题就都有了明确的排查方向:前者查sys.path和运行方式,后者查encoding参数。




需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询