Python第八课:彻底搞懂文件路径、读取与写入

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
  • 相对路径:相对于程序当前的“当前工作目录”(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...

解决方案

  1. 最佳实践:永远不要手动拼接路径字符串!使用 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.txtfile.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.joinpathlib

代码如何知道文件在哪里?

在上一部分中,我们学习了文件路径的基础知识。但有一个关键问题还没有解答:当我们在代码中使用相对路径(如 "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.executablesys.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.executablesys.argv[0] 来定位可执行文件的位置,但需要额外处理。不过对于初学者,先掌握常规用法即可。


5. 修改当前工作目录(谨慎使用)

虽然我们不建议依赖 CWD,但有时确实需要临时改变工作目录。可以使用 os.chdir(path)Path.chdir()

import os os.chdir("/path/to/new/dir")print(os.getcwd())# 已改变

但是要小心:改变工作目录会影响整个进程,可能导致其他相对路径失效。除非有充分理由,否则应避免。


6. 总结

通过本部分的学习,你应该明白了:

  • 当前工作目录是相对路径的参照点,但它是不稳定的。
  • 使用 __file__ 可以构建相对于脚本文件的绝对路径,让你的程序具备可移植性。
  • 推荐使用 pathlib 来简化路径操作。

现在,我们已经解决了“文件在哪里”的问题,可以安全地定位到任何文件。因为篇幅原因,下一部分,我将放到下一篇:Python第九课:文件操作、读取与写入,到时候正式进入文件操作的核心:如何读取文件内容,包括一次性读取、逐行读取,以及处理编码问题。

Read more

C++ STL map 系列全方位解析:从基础使用到实战进阶

C++ STL map 系列全方位解析:从基础使用到实战进阶

🔥草莓熊Lotso:个人主页 ❄️个人专栏: 《C++知识分享》《Linux 入门到实践:零基础也能懂》 ✨生活是默默的坚持,毅力是永久的享受! 🎬 博主简介: 文章目录 * 前言: * 一. map 核心概念:键值对与红黑树底层 * 1.1 什么是 map? * 1.2 关键类型定义 * 二. map 基础操作:构造、遍历与增删查改 * 2.1 构造与初始化 * 2.2 迭代器遍历 * 2.3 插入操作(insert) * 2.4 查找与删除(find/erase) * 2.5 核心特性:operator [] 的多功能性 * 三.

By Ne0inhk

c++随笔记录

目录 1.sizeof和strlen的区别 2.三目运算符 3.一维数组名作用 4.二维数组名作用 5.函数的声明 6指针和引用的区别 7.空指针和野指针 8.const关键字 9.指针和数组 10.指针和函数 11.结构体 12.volatile关键字 13.static关键字 14.define与const区别 15.new/delete与malloc/free区别 16.define与typedef区别 二 c++核心编程(封装、继承、多态) 2.1封装-类 1.类和结构体区别 2.继承 3.构造函数 4.构造函数调用规则

By Ne0inhk

常搞混的PLC编程语言ST、STL、SCL到底有啥差别

ST(结构化文本)、SCL(结构化控制语言)、STL(语句表)是工业自动化领域中 PLC(可编程逻辑控制器)常用的编程语言,三者在语法风格、应用场景、执行逻辑上差异显著。以下从定义本质、语法特征、应用场景、核心区别 四个维度详细解析: 一、核心定义与本质 语言全称本质定位所属标准STLStatement List(语句表)汇编级的低级指令语言,基于 PLC 的指令集,逐条执行IEC 61131-3(可选,不同厂商语法差异大)STStructured Text(结构化文本)类 Pascal/C 的高级文本语言,结构化、模块化IEC 61131-3 标准语言SCLStructured Control Language(结构化控制语言)西门子对 ST 的 “定制扩展版”,核心兼容

By Ne0inhk
C++ 入门全指南:从发展史到第一个程序,命名空间 + 输入输出手把手讲

C++ 入门全指南:从发展史到第一个程序,命名空间 + 输入输出手把手讲

目录 一、C++的发展历史 1.发展历史 2.C++的版本更新 3.C++的参考文档 二、C++的学习建议 1.C++的应用领域: 2.学习书籍推荐: 三、C++的第一个程序 四、命名空间 1.namespace的价值: 2.namespace的定义: 1)使用namespace来命名空间,以及使用命名空间(详解见注释): 2)命名空间的嵌套使用 3)多文件定义的命名空间问题 3.namespace命名空间的使用: 1)指定命名空间访问 2)using将命名空间中某个成员展开 3)展开命名空间中全部成员 五、C++输入&输出

By Ne0inhk