Python第八课:彻底搞懂文件路径、读取与写入
文章目录
引言
“我明明把文件放在脚本旁边了,为什么 Python 说找不到?”这是初学者在文件操作中最常遇到的问题之一。归根结底,是因为没有正确理解文件路径和当前工作目录。文件操作看似简单,但若不了解背后的机制,很容易掉进坑里。
本文将深入浅出地介绍 Python 文件操作的三大核心:文件路径、读取文件、写入文件。学完本文,你将能够自信地处理各种文件读写任务,并能轻松应对跨平台开发中的路径问题。
在开始之前先检查一下你的装备吧!!!
python环境不会装的看这里:从安装到Hello World:Python环境搭建完整指南
python编辑器不会装的看这里:零基础Python入门:手把手教你安装Python、新版PyCharm和VS Code
文件路径
在开始读写文件之前,Python 必须先知道文件在哪里。这个“位置信息”就是文件路径。如果把计算机比作一个巨大的文件柜,路径就是指引你找到正确抽屉和文件夹的索引。如果路径错了,程序就会抛出 FileNotFoundError。所以,掌握文件路径是文件操作的第一步,也是最容易踩坑的地方。
1. 什么是文件路径?
文件路径是一个字符串,它描述了文件在文件系统中的位置。根据起点不同,路径分为两种:
- 绝对路径:从文件系统的根目录开始,完整地指向一个文件。它不依赖任何外部条件,无论你在哪里运行程序,这个路径都能找到同一个文件。
- Windows 示例:
C:\Users\Alice\Documents\report.pdf - Linux/macOS 示例:
/home/alice/Documents/report.pdf
- Windows 示例:
- 相对路径:相对于程序当前的“当前工作目录”(Current Working Directory, CWD)来描述文件位置。它更简洁,但依赖于程序运行时的环境。
- 示例:如果当前工作目录是
C:\Users\Alice,那么相对路径Documents\report.pdf就等同于绝对路径C:\Users\Alice\Documents\report.pdf。 - 特殊符号:
.表示当前目录本身(通常可以省略)。..表示上一级目录。例如..\data\input.txt表示从当前目录的父目录下的data文件夹中找input.txt。
- 示例:如果当前工作目录是
为什么需要两种路径?
- 绝对路径的优点是确定性,适合指向系统固定位置的文件(如配置文件)。但缺点也很明显:如果程序要移植到另一台电脑,或者文件被移动,路径就会失效。
- 相对路径的灵活性更高,适合在项目内部引用文件。只要整个项目文件夹被移动,内部相对关系不变,程序就能正常工作。绝大多数日常开发都优先使用相对路径。
2. 路径分隔符的跨平台陷阱
不同操作系统使用不同的符号来分隔目录:
| 操作系统 | 路径分隔符 | 示例 |
|---|---|---|
| Windows | 反斜杠 \ | C:\Users\name |
| Linux/macOS | 正斜杠 / | /home/name |
有个问题!
在 Python 字符串中,反斜杠 \ 是一个转义字符,比如 \n 表示换行,\t 表示制表符。如果你在 Windows 上直接写 "C:\Users\name",Python 会把 \U 解析为 Unicode 转义,导致报错:
>>> path ="C:\Users\name" SyntaxError:(unicode error)'unicodeescape' codec can't decode bytes...解决方案:
- 最佳实践:永远不要手动拼接路径字符串!使用 Python 标准库提供的工具,它们会自动处理分隔符。
使用双反斜杠:每个反斜杠写两次,转义自身。
path ="C:\\Users\\name\\file.txt"使用正斜杠:Windows 也接受正斜杠作为路径分隔符,这是最省事的办法。
path ="C:/Users/name/file.txt"使用原始字符串:在字符串前加 r,让反斜杠保持原样。
path =r"C:\Users\name\file.txt"3. 传统方式:os.path 模块
os.path 是 Python 早期就有的路径操作模块,提供了一系列函数来处理路径字符串。
常用函数
| 函数 | 描述 |
|---|---|
os.path.join(a, b, ...) | 智能拼接路径,自动添加正确的分隔符。 |
os.path.abspath(path) | 将相对路径转换为绝对路径。 |
os.path.exists(path) | 检查路径是否存在(文件或目录)。 |
os.path.isdir(path) | 判断是否为目录。 |
os.path.isfile(path) | 判断是否为文件。 |
os.path.basename(path) | 返回路径中的最后一部分(文件名)。 |
os.path.dirname(path) | 返回路径中的目录部分。 |
os.path.splitext(path) | 将路径拆分为 (文件名, 扩展名),如 ('file', '.txt')。 |
os.path.expanduser(path) | 将 ~ 扩展为当前用户的家目录。 |
示例代码
import os # 拼接路径 folder ="data" filename ="input.txt" file_path = os.path.join(folder, filename)print(file_path)# 输出:data/input.txt(Windows 上为 data\input.txt)# 获取绝对路径 abs_path = os.path.abspath(file_path)print(abs_path)# 例如:/home/username/project/data/input.txt# 检查文件是否存在if os.path.exists(file_path):print("文件存在")else:print("文件不存在")# 分离文件名和扩展名 name, ext = os.path.splitext("report.pdf")print(name)# reportprint(ext)# .pdf# 获取家目录下的文件 home_file = os.path.expanduser("~/documents/notes.txt")print(home_file)# /home/username/documents/notes.txt注意事项
os.path.join只处理字符串拼接,不会检查路径是否真实存在。- 所有函数都返回字符串,你需要自己管理字符串操作。
4. 现代方式:pathlib 模块
从 Python 3.4 开始,官方引入了 pathlib 模块,它将路径表示为对象,提供了更直观、更面向对象的 API。目前 pathlib 已经成为处理路径的首选方式。
核心类:Path
Path 可以代表文件或目录的路径。你可以通过传入字符串创建 Path 对象,然后使用 / 运算符拼接路径,就像在命令行中操作一样。
常用方法
| 方法 / 属性 | 描述 |
|---|---|
Path(path_str) | 创建 Path 对象。 |
/ 运算符 | 拼接路径,例如 Path("data") / "sub" / "file.txt"。 |
Path.cwd() | 获取当前工作目录。 |
Path.home() | 获取当前用户的家目录。 |
path.resolve() | 将相对路径解析为绝对路径,并解析符号链接。 |
path.exists() | 检查路径是否存在。 |
path.is_dir() / is_file() | 判断是目录还是文件。 |
path.name | 获取路径的最后一部分(文件名)。 |
path.stem | 获取文件名(不含扩展名)。 |
path.suffix | 获取文件扩展名。 |
path.parent | 获取父目录路径(可链式调用,如 path.parent.parent)。 |
path.glob(pattern) | 使用通配符匹配目录下的文件,返回生成器。 |
path.iterdir() | 遍历目录下的所有项。 |
path.mkdir(parents=True) | 创建目录,parents=True 可同时创建缺失的父目录。 |
path.read_text(encoding) | 读取文件内容为字符串。 |
path.write_text(data, encoding) | 将字符串写入文件。 |
示例代码
from pathlib import Path # 创建 Path 对象 base_dir = Path("project") data_file = base_dir /"data"/"input.txt"# 使用 / 拼接print(data_file)# 输出:project/data/input.txt(自动使用系统分隔符)# 获取绝对路径 abs_path = data_file.resolve()print(abs_path)# 例如:/home/username/project/data/input.txt# 检查存在性if data_file.exists():print("文件存在")else:# 创建父目录(如果不存在) data_file.parent.mkdir(parents=True, exist_ok=True)# 然后可以创建文件...# 获取文件信息print("文件名:", data_file.name)# input.txtprint("文件名(无扩展):", data_file.stem)# inputprint("扩展名:", data_file.suffix)# .txtprint("父目录:", data_file.parent)# project/data# 遍历目录for child in Path(".").iterdir():if child.is_file():print(f"文件: {child.name}")elif child.is_dir():print(f"目录: {child.name}")# 使用 glob 查找所有 .txt 文件for txt_file in Path("data").glob("*.txt"):print(txt_file)# 读取/写入文件的简洁方式 Path("hello.txt").write_text("Hello, world!", encoding="utf-8") content = Path("hello.txt").read_text(encoding="utf-8")print(content)为什么推荐 pathlib?
- 可读性强:路径操作像自然语言,
/运算符直观。 - 面向对象:路径本身带有方法,无需到处调用
os.path函数。 - 跨平台友好:内部自动处理分隔符,返回的字符串也是平台原生格式。
- 功能丰富:涵盖了几乎所有路径操作,甚至包括文件读写、目录创建等,减少了代码量。
5. 路径中的特殊符号
.(当前目录):在相对路径中,./file.txt和file.txt是等价的,通常省略。..(父目录):用于向上回溯。例如../images/logo.png表示从当前目录的父目录下的images文件夹中找logo.png。~(用户家目录):在 Unix-like 系统和 Windows 中,~都代表当前用户的家目录。在 Windows 上,它通常对应C:\Users\用户名。Python 的os.path.expanduser("~/file.txt")和pathlib.Path.home() / "file.txt"能够跨平台正确处理这一扩展,让你轻松访问用户目录下的文件。
6. 常见错误
- 错误1:在 Windows 上直接写
"C:\Users\name\file.txt"导致转义问题。
✅ 应使用原始字符串或正斜杠。 - 错误2:认为相对路径总是相对于脚本文件的位置。
✅ 实际上,相对路径是相对于“当前工作目录”,这一点将在第二部分详细说明。 - 错误3:手动拼接路径字符串(如
folder + "/" + file),容易出错且跨平台兼容性差。
✅ 应使用os.path.join或pathlib。
代码如何知道文件在哪里?
在上一部分中,我们学习了文件路径的基础知识。但有一个关键问题还没有解答:当我们在代码中使用相对路径(如 "data/input.txt")时,Python 到底从哪个目录开始寻找这个文件?答案取决于 当前工作目录。如果不理解这个概念,你的程序很可能在别人的电脑上运行失败,甚至在自己电脑上换个位置运行就会报错。
1. 当前工作目录(CWD)
当前工作目录(Current Working Directory,简称 CWD)是操作系统为每个正在运行的进程维护的一个目录。当你在程序中使用相对路径时,Python 会在这个目录下查找文件。可以把它理解为程序运行时的“家”。
如何获取当前工作目录?
使用 os.getcwd() 或 pathlib.Path.cwd():
import os from pathlib import Path print(os.getcwd())# 例如:C:\Users\Alice\projectprint(Path.cwd())# 同样输出当前工作目录当前工作目录是如何确定的?
- 如果你在命令行中运行脚本,比如
python C:\project\script.py,那么当前工作目录通常是命令行当前所在的目录,而不是脚本所在的目录。 - 如果你在 IDE(如 PyCharm、VS Code)中运行,当前工作目录通常是项目根目录,具体取决于 IDE 的设置。
- 如果你双击运行脚本,当前工作目录可能是脚本所在的目录,但也不一定(取决于系统)。
这种不确定性正是问题的根源。请看下面的例子:
假设项目结构如下:
C:\project\ │ ├── script.py └── data\ └── input.txt script.py 内容:
withopen("data/input.txt","r")as f:print(f.read())情况一:在命令行中,先切换到 C:\project 目录,然后运行 python script.py → 成功,因为当前工作目录是 C:\project,相对路径 data/input.txt 可以正确找到。
情况二:在命令行中,当前目录是 C:\,然后运行 python project\script.py → 失败!因为当前工作目录是 C:\,Python 会尝试打开 C:\data\input.txt,但文件实际在 C:\project\data\input.txt,所以抛出 FileNotFoundError。
情况三:在 IDE 中,如果 IDE 将工作目录设置为 C:\project,则成功;如果设置为其他目录,则失败。
由此可见,依赖当前工作目录的相对路径是不稳定的。为了让程序在任何环境下都能正确找到文件,我们需要一种不依赖 CWD 的方法。
2. 使用 file 构建绝对路径
__file__ 是一个特殊的 Python 变量,它包含了当前脚本文件的绝对路径(在大多数情况下)。利用这个变量,我们可以获取脚本所在的目录,然后基于这个目录构建相对于脚本的路径。
如何获取脚本所在目录?
import os from pathlib import Path # 传统方式 script_dir = os.path.dirname(os.path.abspath(__file__)) file_path = os.path.join(script_dir,"data","input.txt")# 现代方式(pathlib) script_dir = Path(__file__).resolve().parent file_path = script_dir /"data"/"input.txt"关键点:
os.path.abspath(__file__)或Path(__file__).resolve()确保我们得到的是绝对路径,避免__file__在某些情况下是相对路径。- 然后通过
os.path.dirname或.parent提取目录部分。 - 最后拼接目标文件的相对路径。
现在,无论当前工作目录是什么,file_path 总是指向 script.py 同级的 data/input.txt。这样程序就具备了可移植性——只要整个项目文件夹被完整移动,内部相对关系不变,文件就能被找到。
完整的示例
import os from pathlib import Path # 基于 __file__ 构建路径 script_dir = Path(__file__).resolve().parent data_file = script_dir /"data"/"input.txt"# 读取文件try:withopen(data_file,"r", encoding="utf-8")as f: content = f.read()print(content)except FileNotFoundError:print(f"文件不存在:{data_file}")注意事项
- 在交互式环境(如 Jupyter Notebook、REPL)中,
__file__可能不存在或不可用。这种情况下,你可能需要手动指定路径或使用其他方法。 - 如果使用打包工具(如 PyInstaller)将脚本打包成可执行文件,
__file__的行为可能会变化,但通常仍有替代方案(如sys.executable或sys.argv[0])。
3. 基于 CWD 与基于 __file__的区别
| 特点 | 基于 CWD | 基于 __file__ |
|---|---|---|
| 路径含义 | 相对于运行时的当前目录 | 相对于脚本文件所在目录 |
| 稳定性 | 受运行环境、IDE 设置影响,不稳定 | 只要项目结构不变,路径就固定 |
| 可移植性 | 差,换台电脑可能失效 | 好,整个项目文件夹移动仍可工作 |
| 适用场景 | 临时脚本、与用户交互的文件 | 项目内部资源文件(配置文件、数据等) |
| 代码复杂度 | 简单,直接写相对路径 | 需要几行额外代码,但一劳永逸 |
什么时候可以放心使用 CWD?
- 当你的程序需要与用户交互,比如让用户通过命令行指定输入文件路径,或者读取用户目录下的文件(如
~/Documents/),此时使用基于 CWD 或绝对路径是合适的。 - 如果只是临时写个脚本,且你确保总是在特定目录下运行,也可以偷懒。
但作为通用原则,对于项目内部的资源文件(如数据文件、配置文件、日志文件),强烈建议基于 __file__ 构建路径。
4. 高级技巧:让 pathlib 更简洁
pathlib 不仅让路径操作更优雅,还能一步到位完成“获取脚本目录 + 拼接路径”的操作:
from pathlib import Path # 获取当前脚本所在目录 BASE_DIR = Path(__file__).resolve().parent # 定义常用目录 DATA_DIR = BASE_DIR /"data" LOG_DIR = BASE_DIR /"logs" CONFIG_FILE = BASE_DIR /"config"/"settings.ini"# 使用 data_file = DATA_DIR /"input.txt" content = data_file.read_text(encoding="utf-8")你甚至可以将 BASE_DIR 定义为模块级别的常量,在整个项目中复用。
处理执行文件被打包的情况
如果你用 PyInstaller 打包成单文件可执行程序,运行时 __file__ 可能指向临时解压目录。此时可以用 sys.executable 或 sys.argv[0] 来定位可执行文件的位置,但需要额外处理。不过对于初学者,先掌握常规用法即可。
5. 修改当前工作目录(谨慎使用)
虽然我们不建议依赖 CWD,但有时确实需要临时改变工作目录。可以使用 os.chdir(path) 或 Path.chdir()。
import os os.chdir("/path/to/new/dir")print(os.getcwd())# 已改变但是要小心:改变工作目录会影响整个进程,可能导致其他相对路径失效。除非有充分理由,否则应避免。
6. 总结
通过本部分的学习,你应该明白了:
- 当前工作目录是相对路径的参照点,但它是不稳定的。
- 使用
__file__可以构建相对于脚本文件的绝对路径,让你的程序具备可移植性。 - 推荐使用
pathlib来简化路径操作。
现在,我们已经解决了“文件在哪里”的问题,可以安全地定位到任何文件。因为篇幅原因,下一部分,我将放到下一篇:Python第九课:文件操作、读取与写入,到时候正式进入文件操作的核心:如何读取文件内容,包括一次性读取、逐行读取,以及处理编码问题。