目录
- 一、I/O操作概述
- 二、文件读写实现原理与操作步骤
- 三、文件基本操作
- 四、shutil模块
- 五、序列化和反序列化
- 六、csv文件
在 Python 程序运行期间,可以使用变量临时存储数据,但是当程序运行结束后,所有数据都将丢失。如果要永久保存数据,需要用到数据库或者文件。数据库适合保存表格化、关联性的数据,而文件适合保存松散的文本信息,或者图片、音视频等独立文件。Python 内置了文件和目录操作模块,可以很方便地读、写文件内容,实现数据的长久保存。
【学习重点】
- 文件的创建、读写和修改
- 文件的复制、删除和重命名
- 文件内容的搜索和替换
- 文件的比较
- 配置文件的读写
- 目录的创建和遍历
一、I/O操作概述
I/O 在计算机中是指 Input/Output,也就是 Stream(流) 的输入和输出。这里的输入和输出是相对于内存来说的,Input Stream(输入流)是指数据从外(磁盘、网络)流进内存,Output Stream 是数据从内存流出到外面(磁盘、网络)。程序运行时,数据都是在内存中驻留,由 CPU 这个超快的计算核心来执行,涉及到数据交换的地方(通常是磁盘、网络操作)就需要 IO 接口。那么这个 IO 接口是由谁提供呢?高级编程语言中的 IO 操作是如何实现的呢?操作系统是个通用的软件程序,其通用目的如下:
- 硬件驱动
- 进程管理
- 内存管理
- 网络管理
- 安全管理
- I/O 管理
操作系统屏蔽了底层硬件,向上提供通用接口。因此,操作 I/O 的能力是由操作系统提供的,每一种编程语言都会把操作系统提供的低级C接口封装起来供开发者使用,Python 也不例外。
二、文件读写实现原理与操作步骤
2.1 文件读写实现原理
文件读写就是一种常见的 IO 操作。那么根据上面的描述,可以推断 Python 也应该封装操作系统的底层接口,直接提供了文件读写相关的操作方法。事实上,也确实如此,而且 Java、PHP 等其他语言也是。那么我们要操作的对象是什么呢?我们又如何获取要操作的对象呢?由于操作 I/O 的能力是由操作系统提供的,且现代操作系统不允许普通程序直接操作磁盘,所以读写文件时需要请求操作系统打开一个对象(通常被称为文件描述符-- file descriptor, 简称 fd),这就是我们在程序中要操作的文件对象。
通常高级编程语言中会提供一个内置的函数,通过接收 "文件路径" 以及 "文件打开模式" 等参数来打开一个文件对象,并返回该文件对象的文件描述符。因此通过这个函数我们就可以获取要操作的文件对象了。这个内置函数在 Python 中叫 open(),在 PHP 中叫 fopen()。
2.2 文件读写操作步骤
不同的编程语言读写文件的操作步骤大体都是一样的,都分为以下几个步骤:
- 打开文件,获取文件描述符
- 操作文件描述符–读/写
- 关闭文件
只是不同的编程语言提供的读写文件的 api 是不一样的,有些提供的功能比较丰富,有些比较简陋。需要注意的是:文件读写操作完成后,应该及时关闭。一方面,文件对象会占用操作系统的资源;另外一方面,操作系统对同一时间能打开的文件描述符的数量是有限制的,在 Linux 操作系统上可以通过 ulimit -n 来查看这个显示数量。如果不及时关闭文件,还可能会造成数据丢失。因为我将数据写入文件时,操作系统不会立刻把数据写入磁盘,而是先把数据放到内存缓冲区异步写入磁盘。当调用 close 方法时,操作系统会保证把没有写入磁盘的数据全部写到磁盘上,否则可能会丢失数据。拓展:
文件描述符(File Descriptor, FD):1.每打开一个文件(或套接字等 IO 资源),操作系统就会为这个文件分配一个文件描述符。2.文件描述符是一个整数,进程内部用它来引用内核为该文件维护的数据结构(如文件状态、读写位置等)。3.如果打开文件后不关闭,它会一直占用一个 FD,并且文件在内核中也保持打开状态(包括对文件锁、缓存、缓冲区等资源的占用)。文件描述符是进程级别的。每个进程有自己的一套文件描述符表(通常是一个数组或列表),索引是 FD(如 0、1、2 通常是 stdin、stdout、stderr)。每个进程打开同一个文件,会在自己进程的文件描述符表中占用一个新的 FD,且通常 FD 数值不同。它们各自独立维护自己的
"文件偏移位置"和缓冲区。但所有进程打开的文件都会关联到系统级的全局文件表,这个全局表再关联到具体的磁盘 inode 等信息。文件描述符有数量限制吗?有,而且是有限的。对单个进程: 通常默认是 1024 个 FD,可以通过 ulimit -n 查看(在类 Unix 系统上)。可以通过 ulimit -n 4096(临时)或配置文件(如 /etc/security/limits.conf)增加。对系统整体: 操作系统也有限制整个系统中可打开的文件数量,这取决于内核参数,比如 Linux 中 /proc/sys/fs/file-max。
三、文件基本操作
3.1 前置知识
3.1.1 什么是文件路径,Python中如何书写文件路径?
关于文件,它有两个关键属性,分别是 "文件名" 和 "路径"。其中,文件名指的是为每个文件设定的名称,而路径则用来指明文件在计算机上的位置。 例如,我的 Win10 笔记本上有一个文件名为 1.txt(句点之后的部分称为文件的 "扩展名",它指出了文件的类型),它的路径在 E:\file\temp,也就是说,该文件位于 E 盘下 file 文件夹中 temp 子文件夹下。
通过文件名和路径可以分析出,1.txt 是一个文本文件,file 和 temp 都是指 "文件夹"(也称为目录)。文件夹可以包含文件和其他文件夹,例如 1.txt 在 temp 文件夹中,该文件夹又在 file 文件夹中。注意,路径中的 E:\ 指的是 "根文件夹",它包含了所有其他文件夹。在 Windows 中,根文件夹名为 E:\,也称为 E: 盘。在 OS X 和 Linux 中,根文件夹是 /。考虑到大部分初学者,文章都使用的是 Windows 风格的根文件夹,如果你在 OS X 或 Linux 上输入交互式环境的例子,请用 / 代替。
另外,附加卷(诸如 DVD 驱动器或 USB 闪存驱动器),在不同的操作系统上显示也不同。在 Windows 上,它们表示为新的、带字符的根驱动器。诸如 D:\ 或 E:\。在 OS X 上,它们表示为新的文件夹,在 /Volumes 文件夹下。在 Linux 上,它们表示为新的文件夹,在 /mnt 文件夹下。同时也要注意,虽然文件夹名称和文件名在 Windows 和 OS X 上是不区分大小写的,但在 Linux 上是区分大小写的。Windows 上的反斜杠以及 OS X 和 Linux 上的正斜杠,在 Windows 上,路径书写使用反斜杠 "\" 作为文件夹之间的分隔符。但在 OS X 和 Linux 上,使用正斜杠 "/" 作为它们的路径分隔符。如果想要程序运行在所有操作系统上,在编写 Python 脚本时,就必须处理这两种情况。
好在,用 os.path.join() 函数来做这件事很简单。如果将单个文件和路径上的文件夹名称的字符串传递给它,os.path.join() 就会返回一个文件路径的字符串,包含正确的路径分隔符。在交互式环境中输入以下代码:
In [3]: import os
In [4]: os.path.join('temp', '1.txt')
Out[4]: 'temp\\1.txt'
因为此程序是在 Windows 上运行的,所以 os.path.join('temp', '1.txt') 返回 'temp\\1.txt'(请注意,反斜杠有两个,因为每个反斜杠需要由另一个反斜杠字符来转义)。如果在 OS X 或 Linux 上调用这个函数,该字符串就会是 'temp/1.txt'。
[root@VM-16-6-centos ~]# python3
Python 3.8.10 (default, Nov 9 2024, 08:11:13)
[GCC 4.8.5 20150623 (Red Hat 4.8.5-44)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import os
>>> os.path.join("temp", "1.txt")
'temp/1.txt'
3.1.2 Python绝对路径和相对路径详解
在介绍绝对路径和相对路径之前,先要了解一下什么是当前工作目录。每个运行在计算机上的程序,都有一个 "当前工作目录"(或 cwd)。所有没有从根文件夹开始的文件名或路径,都假定在当前工作目录下。注意,虽然文件夹是目录的更新的名称,但当前工作目录(或当前目录)是标准术语,没有当前工作文件夹这种说法。在 Python 中,利用 os.getcwd() 函数可以取得当前工作路径的字符串,还可以利用 os.chdir() 改变它。例如,在交互式环境中输入以下代码:
E:\projects\pyCode2025>ipython
Python 3.12.8 (tags/v3.12.8:2dc476b, Dec 3 2024, 19:30:04) [MSC v.1942 64 bit (AMD64)]
Type 'copyright', 'credits' or 'license' for more information
IPython 8.32.0 -- An enhanced Interactive Python. Type '?' for help.
In [1]: import os
In [2]: os.getcwd()
Out[2]: 'E:\\projects\\pyCode2025'
In [3]: os.chdir('E:\\projects\\pyCode2025\\base')
In [4]: os.getcwd()
Out[4]: 'E:\\projects\\pyCode2025\\base'
可以看到,原本当前工作路径为 'E:\\projects\\pyCode2025',通过 os.chdir() 函数,将其改成了 'E:\\projects\\pyCode2025\\base'。需要注意的是,如果使用 os.chdir() 修改的工作目录不存在,Python 解释器会报错,例如:

了解了当前工作目录的具体含义之后,接下来介绍绝对路径和相对路径各自的含义和用法。
什么是绝对路径与相对路径? 明确一个文件所在的路径,有 2 种表示方式,分别是:
- 绝对路径:总是从根文件夹开始,Window 系统中以盘符(C: 、D:)作为根文件夹,而 OS X 或者 Linux 系统中以 / 作为根文件夹。
- 相对路径:指的是文件相对于当前工作目录所在的位置。例如,当前工作目录为
"E:\\projects\\pyCode2025\\base",若文件 test.py 就位于这个 base 文件夹下,则 test.py 的相对路径表示为".\test.py"(其中.\就表示当前所在目录)。
在使用相对路径表示某文件所在的位置时,除了经常使用 .\ 表示当前所在目录之外,还会用到 ..\ 表示当前所在目录的父目录。图例:

以上图为例,如果当前工作目录设置为 C:\bacon,则这些文件夹和文件的相对路径和绝对路径,就对应为该图右侧所示的样子。Python os.path 模块(Python 常用模块(三):os.path模块 👉 查看文章)提供了一些函数,可以实现绝对路径和相对路径之间的转换,以及检查给定的路径是否为绝对路径,比如说:
- 调用 os.path.abspath(path) 将返回 path 参数的绝对路径的字符串,这是将相对路径转换为绝对路径的简便方法
- 调用 os.path.isabs(path),如果参数是一个绝对路径,就返回 True,如果参数是一个相对路径,就返回 False。
- 调用 os.path.relpath(path, start) 将返回从 start 路径到 path 的相对路径的字符串。如果没有提供 start,就使用当前工作目录作为开始路径。
- 调用 os.path.dirname(path) 将返回一个字符串,它包含 path 参数中最后一个斜杠之前的所有内容
- 调用 os.path.basename(path) 将返回一个字符串,它包含 path 参数中最后一个斜杠之后的所有内容。
在交互式环境中尝试上面提到的函数:
In [7]: os.getcwd()
Out[7]: 'E:\\projects\\pyCode2025\\base'
In [8]: os.path.abspath('.')
Out[8]: 'E:\\projects\\pyCode2025\\base'
In [9]: os.path.abspath('.\\day13-miniweb框架')
Out[9]: 'E:\\projects\\pyCode2025\\base\\day13-miniweb框架'
In [10]: os.path.isabs('.')
Out[10]: False
In [11]: os.path.isabs(os.path.abspath('.'))
Out[11]: True
In [12]: os.path.relpath('E:\\projects\\pyCode2025\\', 'E:\\')
Out[12]: 'projects\\pyCode2025'
In [13]: os.path.relpath('E:\\', 'E:\\projects\\pyCode2025\\')
Out[13]: '..\\..'
In [14]: temp_path = r'E:\projects\pyCode2025\base\test_new.txt'
In [15]: os.path.basename(temp_path)
Out[15]: 'test_new.txt'
In [16]: os.path.dirname(temp_path)
Out[16]: 'E:\\projects\\pyCode2025\\base'
注意,由于读者的系统文件和文件夹可能与我的不同,所以读者不必完全遵照本节的例子,根据自己的系统环境对本节代码做适当调整即可。\ 在 Python 中是转义字符,所以时常会有冲突。为了避坑,Windows 的绝对路径通常要稍作处理,可以写成以下两种方式:
# 会出问题这样写
temp_path = 'E:\projects\pyCode2025\base\test_new.txt'
temp_path = 'E:\\projects\\pyCode2025\\base\\test_new.txt'
#将'\'替换成'\\'
temp_path = r'E:\projects\pyCode2025\base\test_new.txt'
#在路径前加上字母r
除此之外,如果同时需要一个路径的目录名称和基本名称,就可以调用 os.path.split() 获得这两个字符串的元组,例如:
In [14]: temp_path = r'E:\projects\pyCode2025\base\test_new.txt'
In [17]: os.path.split(temp_path)
Out[17]: ('E:\\projects\\pyCode2025\\base', 'test_new.txt')
注意,可以调用 os.path.dirname() 和 os.path.basename(),将它们的返回值放在一个元组中,从而得到同样的元组。但使用 os.path.split() 无疑是很好的快捷方式。同时,如果提供的路径不存在,许多 Python 函数就会崩溃并报错,但好在 os.path 模块提供了以下函数用于检测给定的路径是否存在,以及它是文件还是文件夹:
- 如果 path 参数所指的文件或文件夹存在,调用 os.path.exists(path) 将返回 True,否则返回 False。
- 如果 path 参数存在,并且是一个文件,调用 os.path.isfile(path) 将返回 True,否则返回 False。
- 如果 path 参数存在,并且是一个文件夹,调用 os.path.isdir(path) 将返回 True,否则返回 False。
下面是在交互式环境中尝试这些函数的结果:
E:\projects\pyCode2025>ipython
Python 3.12.8 (tags/v3.12.8:2dc476b, Dec 3 2024, 19:30:04) [MSC v.1942 64 bit (AMD64)]
Type 'copyright', 'credits' or 'license' for more information
IPython 8.32.0 -- An enhanced Interactive Python. Type '?' for help.
In [1]: import os
In [3]: os.path.exists(r'E:\projects')
Out[3]: True
In [4]: os.path.exists('e:/temp')
Out[4]: False
In [5]: os.path.isdir('.')
Out[5]: True
In [6]: os.path.isdir('./test.py')
Out[6]: False
In [7]: os.path.isfile('./test.py')
Out[7]: True
3.1.3 Python文件基本操作
Python 中,对文件的操作有很多种,常见的操作包括创建、删除、修改权限、读取、写入等,这些操作可大致分为以下 2 类:
- 删除、修改权限:作用于文件本身,属于系统级操作
- 写入、读取:是文件最常用的操作,作用于文件的内容,属于应用级操作
其中,对文件的系统级操作功能单一,比较容易实现,可以借助 Python 中的专用模块(os、sys 等),并调用模块中的指定函数来实现。例如,假设如下代码文件的同级目录中有一个文件 "test.py",通过调用 os 模块中的 remove 函数,可以将该文件删除,具体实现代码如下:
E:\projects\pyCode2025\base>ipython
Python 3.12.8 (tags/v3.12.8:2dc476b, Dec 3 2024, 19:30:04) [MSC v.1942 64 bit (AMD64)]
Type 'copyright', 'credits' or 'license' for more information
IPython 8.32.0 -- An enhanced Interactive Python. Type '?' for help.
In [1]: import os
In [2]: os.remove('test.py')
# 文件不存在报错
In [3]: os.remove('test.py')
---------------------------------------------------------------------------
FileNotFoundError Traceback (most recent call last)
Cell In[3], line 1
----> 1 os.remove('test.py')
FileNotFoundError: [WinError 2] 系统找不到指定的文件。: 'test.py'
而对于文件的应用级操作,通常需要按照固定的步骤进行操作,且实现过程相对比较复杂,在 《2.2 文件读写操作步骤》 小节中已经讲解过,此处细化一下与 Python 对应起来,文件的应用级操作可以分为以下 3 步,每一步都需要借助对应的函数实现:
- 打开文件:使用 open() 函数,该函数会返回一个文件对象
- 对已打开文件做读/写操作:读取文件内容可使用 read()、readline() 以及 readlines() 函数;向文件中写入内容,可以使用 write() 函数。
- 关闭文件:完成对文件的读/写操作之后,最后需要关闭文件,可以使用 close() 函数。
再次强调: 一个文件,必须在打开之后才能对其进行操作,并且在操作结束之后,还应该将其关闭,这 3 步的顺序不能打乱。以上操作文件的各个函数,会各自作为一节在后续文章中进行详细介绍。
3.2 Python open()函数详解
我们先来看下在 Python、PHP 和 C语言中打开文件的函数定义:
# Python
open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)
# PHP
resource fopen(string $filename , string $mode [, bool $use_include_path = false [, resource $context ]])
# C语言
int open(const char *pathname, int flags);
会发现以上3种编程语言内置的打开文件的方法接收的参数中,除了都包含一个 "文件路径名称",还会包含一个 mode 参数(C语言的 open 函数中的 flags 参数作用相似)。这个 mode 参数定义的是打开文件时的模式,常见的文件打开模式有:只读、只写、可读可写、只追加。不同的编程语言中对文件打开模式的定义有些微小的差别,我们来看下 Python 中的文件打开模式有哪些。Python 中 open() 函数用于创建或打开指定文件,该函数的常用语法格式如下:
file_obj = open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None,
closefd=True, opener=None)
Python 的 open() 方法参数说明:
- file_obj:表示要创建的文件对象,类型可能是
<class '_io.TextIOWrapper'>亦或者是<class '_io.BufferedReader'> - file:str or path-like object,要打开的文件路径,可以是绝对或相对路径,也可以是
Path对象 - mode:str,可选参数,用于指定文件的打开模式。可选的打开模式如下方所示。如果不写,则默认以只读(r)模式打开文件。
- 通用读写模式相关参数: 本组参数可以与文件格式参数组合使用,用于设置基本读、写操作权限,以及文件指针初始位置
'r': 只读模式。默认。以只读方式打开一个文件,文件指针被定位到文件头的位置。如果该文件不存在,则会报错'w': 只写模式。打开一个文件只用于写入。如果该文件已存在,则打开文件,清空文件内容,并把文件指针定位到文件头位置开始编辑。如果该文件不存在,则创建新文件,打开并编辑'a': 追加模式。打开一个文件用于追加,仅有只写权限,无权读操作。如果该文件已存在,文件指针被定位到文件尾。新内容被写入到原内容之后。如果该文件不存在,则创建新文件并写入
- 特殊读写模式相关参数:
'+': 更新模式。打开一个文件进行更新,具有可读、可写权限。注意,该模式不能单独使用, 需要与r/w/a模式组合使用。打开文件后,文件指针的位置由r/w/a组合模式决定'x': 只写模式。新建一个文件,打开并写入内容,如果该文件已存在,则会报错
- 文件格式相关参数: 本组参数可以与其他模式参数组合使用,用于指定打开文件的格式,需要根据要打开文件的类型进行选择,此模式不能单独使用
-
b: 二进制模式,以二进制格式打开文件,一般用于非文本文件,如图片、音视频等 -
t: 文本模式,默认以文本格式打开文件,一般用于文本文件,通常省略,省略不影响功能,代码也更简洁。即在实际使用中,如果你没有特别说明模式是'b'(即二进制),那么 Python 默认会使用't'模式(文本模式)。也就是说:'r'就是'rt'的简写,两者完全等价,'w'就是'wt'的简写,两者完全等价,包括后续的组合模式'r+'与'rt+',也是完全一样的。反正你记住,模式中没有看到'b'出现,一律按文本格式处理。# 默认就是文本模式 f1 = open("demo.txt", "r") # 默认含 "t" f2 = open("demo.txt", "rt") # 显式使用 "t" # 都是等价的
-
- 组合模式: 文件格式与通用读写模式可以组合使用,另外通过组合+模式可以为只读、只写模式增加写、读的权限
'r+': 文本格式读写。以文本格式打开一个文件用于读、写。文件指针被定位到文件头的位置。新写入的内容将覆盖掉原有文件部分或全部内容;如果该文件不存在,则会报错'rb': 二进制格式只读。以二进制格式打开一个文件,只能够读取。文件指针被定位到文件头的位置。一般用于非文本文件,如图片等'rb+': 二进制格式读写。以二进制格式打开一个文件用于读、写。文件指针被定位到文件头的位置。新写入的内容将覆盖掉原有文件部分或全部内容;如果该文件不存在,则会报错。一般用于非文本文件'w+': 文本格式写读。以文本格式打开一个文件用于写、读。如果该文件已存在,则打开文件,清空原有内容,进入编辑模式。如果该文件不存在,则创建新文件,打开并执行写、读操作'wb': 二进制格式只写。以二进制格式打开一个文件,只能够写入。如果该文件已存在,则打开文件,清空原有内容,进入编辑模式。如果该文件不存在,则创建新文件,打开并执行只写操作。一般用于非文本文件'wb+': 二进制格式写读。以二进制格式打开一个文件用于写、读。如果该文件已存在,则打开文件,清空原有内容,进入编辑模式。如果该文件不存在,则创建新文件,打开并执行写、读操作。一般用于非文本文件'a+': 文本格式读写。以文本格式打开一个文件用于读、写。如果该文件已存在,则打开文件,文件指针被定位到文件尾的位置,新写入的内容添加在原有内容的后面。如果该文件不存在,则创建新文件,打开并执行写、读操作'ab': 二进制格式只写。以二进制格式打开一个文件用于追加写入。如果该文件已存在,则打开文件,文件指针被定位到文件尾的位置,新写入的内容在原有内容的后面。如果该文件不存在,则创建新文件,打开并执行只写操作'ab+': 二进制格式读写。以二进制格式打开一个文件用于追加写入。如果该文件已存在,则打开文件,文件指针被定位到文件尾的位置,新写入的内容在原有内容的后面。如果该文件不存在,则创建新文件,打开并执行写、读操作
- ps: 以二进制模式打开的文件(包含
'b'),返回文件内容为字节对象,而不进行任何解码。在文本模式(包含't'时,返回文件内容为字符串,已经解码。
- 通用读写模式相关参数: 本组参数可以与文件格式参数组合使用,用于设置基本读、写操作权限,以及文件指针初始位置
- buffering:int,可选参数,用于指定对文件做读写操作时,是否使用缓冲区(本小节后续会详细介绍)
- encoding:str,手动设定打开文件时所使用的编码格式,不同平台的 ecoding 参数值也不同,以 Windows 为例,其默认为 cp936(可以理解为 GBK 编码),仅在文本模式下有效。建议,使用文件对象时,一定要指定编码,而不是使用默认编码
- errors:编码错误处理策略,例如
'ignore'、'replace'、'strict' - newline:str,控制换行符的处理(
None,'','\n','\r','\r\n') - closefd:bool,如果传入的是文件描述符(int),是否在关闭文件时也关闭该描述符(默认 True)
- opener:callable,可选的自定义打开函数,通常用于底层控制,如传递自定义 flags 给
os.open
文件打开模式,直接决定了后续可以对文件做哪些操作。例如,使用 r 模式打开的文件,后续编写的代码只能读取文件,而无法修改文件内容。 将以上几个容易混淆的文件打开模式的功能做了很好的对比:

【示例】默认打开 "base_demo.py" 文件。
# -*- coding: utf-8 -*-
# @Time : 2025-05-12 5:36
# @Author : AmoXiang
# @File : 1.open打开文件.py
# @Software: PyCharm
# @Blog: https://blog.youkuaiyun.com/xw1680
# 当前程序文件同目录下没有 base_demo.py 文件
file = open('base_demo.py')
print(file)
当以默认模式打开文件时,默认使用 r 权限,由于该权限要求打开的文件必须存在,因此运行此代码会报如下错误:

现在,在程序文件同目录下,打开存在的文件 test.py,并再次运行该程序,其运行结果为:
<_io.TextIOWrapper name='test.py' mode='r' encoding='cp936'>
可以看到,当前输出结果中,输出了 file 文件对象的相关信息,包括打开文件的名称、打开模式、打开文件时所使用的编码格式。使用 open() 打开文件时,默认采用 GBK 编码。但当要打开的文件不是 GBK 编码格式时,可以在使用 open() 函数时,手动指定打开文件的编码格式,例如:
file = open('test.py', encoding='utf-8')
注意,手动修改 encoding 参数的值,仅限于文件以文本的形式打开,也就是说,以二进制格式打开时,不能对 encoding 参数的值做任何修改,否则程序会抛出 ValueError 异常,如下所示:
# 执行该程序会报错
# ValueError: binary mode doesn't take an encoding argument
file = open('test.py', 'rb', encoding='utf-8')
print(file)
open() 是否需要缓冲区?(了解)
缓冲区(buffer)是内存中的一块临时存储空间,用于暂时保存要写入或读取的数据。通常情况下、建议大家在使用 open() 函数时打开缓冲区,即不需要修改 buffing 参数的值。如果 buffing 参数的值为 0(或者 False),则表示在打开指定文件时不使用缓冲区;buffering=1 ➜ 行缓冲(只对文本文件有效,每一行处理一次);如果 buffing 参数值为大于 1 的整数,该整数用于指定缓冲区的大小(单位是字节);如果 buffing 参数的值为负数,则代表Python 自动决定缓冲区的大小(推荐)。为什么呢?原因很简单,目前为止计算机内存的 I/O 速度仍远远高于计算机外设(例如键盘、鼠标、硬盘等)的 I/O 速度,如果不使用缓冲区,则程序在执行 I/O 操作时,内存和外设就必须进行同步读写操作,也就是说,内存必须等待外设输入(输出)一个字节之后,才能再次输出(输入)一个字节。这意味着,内存中的程序大部分时间都处于等待状态。而如果使用缓冲区,则程序在执行输出操作时,会先将所有数据都输出到缓冲区中,然后继续执行其它操作,缓冲区中的数据会有外设自行读取处理;同样,当程序执行输入操作时,会先等外设将数据读入缓冲区中,无需同外设做同步读写操作。大白话: 计算机中有两种设备:内存(Memory):非常快。外设(如硬盘、键盘等):比较慢。如果不使用缓冲区,每次读取或写入都要内存和外设“面对面”交流:
👩💻 内存:喂,硬盘,你准备好了吗?我要写一个字节了!
🧱 硬盘:(慢慢地)我准备好了……你可以写了。
这样一来,内存就只能等硬盘慢吞吞地读/写完一个字节,然后再处理下一个,程序运行会非常慢。使用缓冲区会怎样?
使用缓冲区后,内存就不需要等待硬盘每次都准备好:
写文件时:数据先写入内存中的缓冲区,等缓冲区满了或手动刷新,再一次性写入硬盘。
读文件时:会先从硬盘读取一大块数据到缓冲区中,然后程序从缓冲区里读取数据,访问更快。
open() 文件对象常用的属性: 成功打开文件之后,可以调用文件对象本身拥有的属性获取当前文件的部分信息,其常见的属性为:
| 属性/方法 | 作用说明 |
|---|---|
file.closed | ✅ 判断文件是否已被关闭,返回 True 或 False |
file.mode | ✅ 返回打开文件时使用的模式,比如 'r'、'w'、'rb' 等 |
file.name | ✅ 返回文件的名称(字符串) |
file.readable() | ✅ 判断文件是否可读,返回 True 或 False |
file.writable() | ✅ 判断文件是否可写,返回 True 或 False |
file.seekable() | ✅ 判断文件是否支持随机访问(即是否能使用 seek() 移动文件指针) |
示例代码:
# -*- coding: utf-8 -*-
# @Time : 2025-05-12 5:36
# @Author : AmoXiang
# @File : 1.open打开文件.py
# @Software: PyCharm
# @Blog: https://blog.youkuaiyun.com/xw1680
f = open("test.py", "r", encoding="utf-8")
print("文件名:", f.name)
print("打开模式:", f.mode)
print("是否已关闭:", f.closed)
print("是否可读:", f.readable()) # True,因为 w+ 支持读写
print("是否可写:", f.writable()) # True
print("是否支持随机访问:", f.seekable())
f.close()
print("关闭后,是否已关闭:", f.closed)
# 运行结果如下:
'''
文件名: test.py
打开模式: r
是否已关闭: False
是否可读: True
是否可写: False
是否支持随机访问: True
关闭后,是否已关闭: True
'''
3.3 以文本格式和二进制格式打开文件,到底有什么区别?
我们知道,open() 函数第二个参数是一个字符串,用于指定文件的打开方式,如果该字符串中出现 'b',则表示以二进制格式打开文件;反之,则以普通的文本格式打开文件。那么,文本文件和二进制文件有什么区别呢?
两种格式的解码区别: 根据我们以往的经验,文本文件通常用来保存肉眼可见的字符,比如 .txt 文件、.c 文件、.dat 文件等,用文本编辑器打开这些文件,我们能够顺利看懂文件的内容。而二进制文件通常用来保存视频、图片、音频等不可阅读的内容,当用文本编辑器打开这些文件,会看到一堆乱码,根本看不懂。实际上,从数据存储的角度上分析,二进制文件和文本文件没有区别,它们的内容都是以二进制的形式保存在磁盘中的。我们之所以能看懂文本文件的内容,是因为文本文件中采用的是 ASCII、UTF-8、GBK 等字符编码,文本编辑器可以识别出这些编码格式,并将编码值转换成字符展示出来。而对于二进制文件,文本编辑器无法识别这些文件的编码格式,只能按照字符编码格式胡乱解析,所以最终看到的是一堆乱码。
文本格式 open() 时的隐式转换: 使用 open() 函数以文本格式打开文件和以二进制格式打开文件,唯一的区别是对文件中换行符的处理不同。在 Windows 系统中,文件中用 "\r\n" 作为行末标识符(即换行符),当以文本格式读取文件时,会将 "\r\n" 转换成 "\n";反之,以文本格式将数据写入文件时,会将 "\n" 转换成 "\r\n"。这种隐式转换换行符的行为,对用文本格式打开文本文件是没有问题的,但如果用文本格式打开二进制文件,就有可能改变文本中的数据(将 "\r\n" 隐式转换为 "\n")。而在 Unix/Linux 系统中,默认的文件换行符就是 "\n",因此在 Unix/Linux 系统中文本格式和二进制格式并无本质的区别。总的来说,为了保险起见,对于 Windows 平台最好用 "b" 打开二进制文件;对于 Unix/Linux 平台,打开二进制文件,可以用 "b",也可以不用。
Python 的 open() 函数在底层,会依次使用以下几层对象来处理文件:
二进制模式: open() → FileIO → BufferedReader/BufferedWriter
# 文本模式: 文本模式是在二进制模式之上再套了一层专门处理"字符"的包装器 —— TextIOWrapper
open() → FileIO → BufferedReader/BufferedWriter → TextIOWrapper(处理字符编码)
文本模式和二进制模式的关键底层差异:
| 层面 | 文本模式(Text Mode) | 二进制模式(Binary Mode) |
|---|---|---|
| 数据结构 | 返回 str(字符) | 返回 bytes(字节) |
| 编码/解码 | 自动执行(如 utf-8) | 无编码解码,原始字节 |
| 底层类 | TextIOWrapper 封装 | BufferedReader、BufferedWriter |
| I/O系统调用 | 会在 TextIOWrapper 中进行解码,处理换行 | 直接调用 read()、write(),传输原始字节 |
| 换行符处理 | Windows 下 \r\n ➜ \n(自动转换) | 原样保留 |
| seek()偏移量 | 按字符偏移,可能因多字节字符而"不准确" | 按字节偏移,精确可靠 |
| 性能 | 编解码开销更大 | 性能更高、低延迟 |
| 跨平台行为 | 会自动适配不同平台的换行符(\n、\r\n) | 无平台兼容处理,原始数据 |
示例代码:
# -*- coding: utf-8 -*-
# @Time : 2025-05-13 0:02
# @Author : AmoXiang
# @File : 2.t模式与b模式的区别.py
# @Software: PyCharm
# @Blog: https://blog.youkuaiyun.com/xw1680
f1 = open("1.txt", "r", encoding="utf-8")
'''
你会发现 f 实际是 TextIOWrapper 对象。它负责:
① 自动从底层的 BufferedReader 获取原始字节流
② 按指定编码(utf-8)解码为 str
③ 替你处理换行符转换
'''
print(type(f1)) # <class '_io.TextIOWrapper'>
# 而如果你打开文件时使用 'rb'
f2 = open("1.txt", "rb")
# 这时 f 就是字节级别的对象,编码解码操作完全交给你自己
print(type(f2)) # <class '_io.BufferedReader'>
# 拓展:
# 1.字节(bytes) 是数据的最小单位,CPU、硬盘、网络都以字节为单位进行通信
# 2.字符(str) 是人类语义单位,它需要依赖编码(如 UTF-8、GBK、ASCII)来表示成字节
# 所以:
# 1.文本模式是: Python 把"看得懂"的字符编码成字节再传给底层系统
# 2.二进制模式是: Python 直接操作字节,尽可能贴近硬件
3.4 Python read()方法:按字节(字符)读取文件
Python 提供了如下 3 种方法,它们都可以帮我们实现读取文件中数据的操作:
- read() 方法:逐个字节或者字符读取文件中的内容
- readline() 方法:逐行读取文件中的内容
- readlines() 方法:一次性读取文件中多行内容
本小节先讲解 read() 方法的用法,readline() 和 readlines() 方法会放到 3.5 小节 中作详细介绍。对于借助 open() 函数,并以可读模式(包括 r、r+、rb、rb+)打开的文件,可以调用 read() 函数逐个字节(或者逐个字符)读取文件中的内容。
如果文件是以文本模式(非二进制模式)打开的,则 read() 函数会逐个字符进行读取;反之,如果文件以二进制模式打开,则 read() 函数会逐个字节进行读取
read() 函数的基本语法格式如下:
In [2]: f.read?
Signature: f.read(size=-1, /)
Docstring:
Read at most size characters from stream.
Read from underlying buffer until we have size characters or we hit EOF.
If size is negative or omitted, read until EOF.
Type: builtin_function_or_method
其中,f 表示已打开的文件对象;size 作为一个可选参数,用于指定一次最多可读取的字符(字节)个数,如果省略,则默认一次性读取
所有内容。
举个例子,首先创建一个名为 test.txt 的文本文件,其内容为:
100天精通Python
https://blog.youkuaiyun.com/xw1680/category_12955592.html
然后在和 test.txt 同目录下,创建一个 read_demo.py 文件,并编写如下语句:
# -*- coding: utf-8 -*-
# @Time : 2025-05-13 0:18
# @Author : AmoXiang
# @File : 3.read_demo.py
# @Software: PyCharm
# @Blog: https://blog.youkuaiyun.com/xw1680
# #以 utf-8 的编码格式打开指定文件
f = open('test.txt', 'r', encoding='utf-8')
# 输出读取到的数据
print(f.read())
# 关闭文件
f.close()
程序执行结果为:

当然,我们也可以通过使用 size 参数,指定 read() 每次可读取的最大字符(或者字节)数,例如:
# -*- coding: utf-8 -*-
# @Time : 2025-05-13 0:18
# @Author : AmoXiang
# @File : 3.read_demo.py
# @Software: PyCharm
# @Blog: https://blog.youkuaiyun.com/xw1680
# #以 utf-8 的编码格式打开指定文件
f = open('test.txt', 'r', encoding='utf-8')
# 输出读取到的数据 结果为: 100天精通
print(f.read(6))
# 关闭文件
f.close()
显然,该程序中的 read() 函数只读取了 test.txt 文件开头的 6 个字符。再次强调,size 表示的是一次最多可读取的字符(或字节)数,因此,即便设置的 size 大于文件中存储的字符(字节)数,read() 函数也不会报错,它只会读取文件中所有的数据。除此之外,对于以二进制格式打开的文件,read() 函数会逐个字节读取文件中的内容。例如:
# 以二进制形式打开指定文件
f = open("test.txt", 'rb+')
# 输出读取到的数据
# b'100\xe5\xa4\xa9\xe7\xb2\xbe\xe9\x80\x9aPython\r\nhttps://blog.youkuaiyun.com/xw1680/category_12955592.html'
print(f.read())
# 关闭文件
f.close()
可以看到,输出的数据为 bytes 字节串。我们可以调用 decode() 方法,将其转换成我们认识的字符串。有关 bytes 字节串,读者可阅读 《100天精通Python——基础篇 2025 第7天:字符串操作全掌握与编码基础剖析》中的 四、编码与解码 小节做详细了解。另外需要注意的一点是,想使用 read() 函数成功读取文件内容,除了严格遵守 read() 的语法外,其还要求 open() 函数必须以可读默认(包括 r、r+、rb、rb+)打开文件。举个例子,将上面程序中 open() 的打开模式改为 w,程序会抛出 io.UnsupportedOperation 异常,提示文件没有读取权限:

read() 函数抛出 UnicodeDecodeError 异常的解决方法: 在使用 read() 函数时,如果 Python 解释器提示 UnicodeDecodeError 异常,其原因在于,目标文件使用的编码格式和 open() 函数打开该文件时使用的编码格式不匹配。举个例子,如果目标文件的编码格式为 GBK 编码,而我们在使用 open() 函数并以文本模式打开该文件时,手动指定 encoding 参数为 UTF-8。这种情况下,由于编码格式不匹配,当我们使用 read() 函数读取目标文件中的数据时,Python 解释器就会提示 UnicodeDecodeError 异常。要解决这个问题,要么将 open() 函数中的 encoding 参数值修改为和目标文件相同的编码格式,要么重新生成目标文件(即将该文件的编码格式改为和 open() 函数中的 encoding 参数相同)。除此之外,还有一种方法:先使用二进制模式读取文件,然后调用 bytes 的 decode() 方法,使用目标文件的编码格式,将读取到的字节串转换成认识的字符串。举个例子:
# 在windows下使用记事本工具,输入以下内容,并以ANSI编码保存到桌面,命名为test.txt
hello,世间种种的诱惑
# 在 Windows 上,"ANSI" 不是一个真正的编码标准,而是一个 模糊的本地编码概念,它依赖于系统的默认区域设置(语言环境)
# 如果你的 Windows 设置为简体中文,ANSI 实际等于 GBK(兼容 GB2312)
# 以二进制形式打开指定文件
f = open(r"C:\Users\amoxiang\Desktop\test.txt", 'r', encoding='utf-8')
# 输出读取到的数据
# 报错: UnicodeDecodeError: 'utf-8' codec can't decode byte 0xa3 in position 5: invalid start byte
print(f.read())
# 关闭文件
f.close()
# 正确方式1: 以二进制方式读取,再用 GBK 解码
with open(r"C:\Users\amoxiang\Desktop\test.txt", 'rb') as f:
byte_data = f.read() # 读取为字节串
# 使用 GBK 编码进行解码
text = byte_data.decode('gbk')
print(text) # 正确输出: hello,世间种种的诱惑
# 正确方式2: 先检测文件的编码格式,以便你正确选择open()函数中的 encoding 参数
# 使用 chardet 库(推荐): pip install chardet
# ps: 只是猜测编码,不是 100% 准确,但对于中文文本文件(GBK、UTF-8、UTF-16)通常很可靠
import chardet
# 读取部分字节内容用于判断编码(通常不需要读取整个文件)
with open(r"C:\Users\amoxiang\Desktop\test.txt", 'rb') as f:
raw_data = f.read(1000) # 读取前 1000 个字节
result = chardet.detect(raw_data)
print(result)
# {'encoding': 'GB2312', 'confidence': 0.99, 'language': 'Chinese'}
# 以二进制形式打开指定文件
f = open(r"C:\Users\amoxiang\Desktop\test.txt", 'r', encoding=result['encoding'])
# 输出读取到的数据
print(f.read())
# 关闭文件
f.close()
输出内容乱码与 UnicodeDecodeError 异常的解决方法是一样的,都是编解码格式不统一导致的。总结三种解决方案:
| 方案 | 描述 |
|---|---|
| ✅ 推荐 1 | 修改 open() 的 encoding 参数,使其与文件编码一致(如 'gbk') |
| ✅ 推荐 2 | 转换文件编码(用文本编辑器或 iconv 工具改为 utf-8) |
| ✅ 推荐 3 | 先 rb 读取为字节,再用 .decode(编码格式) 方法手动转换 |
在给个示例:
filename = './test20250513-3.txt'
f = open(filename, 'w+', encoding='utf-8')
f.write('Amo教育')
f.close()
f = open(filename, encoding='utf-8')
print(1, f.read(1)) # 按字符 ==> 1 A
print(2, f.read(2)) # 2 mo
print(3, f.read()) # 3 教育
f.close()
f = open(filename, 'rb')
print(4, f.read(1)) # 按字节 4 b'A'
print(5, f.read(2)) # 5 b'mo'
# gbk编码: 两个字节表示一个字符
print(6, 'Amo教育'.encode('gbk')) # b'Amo\xbd\xcc\xd3\xfd'
# utf-8编码: 三个字节表示一个字符
print(7, f.read()) # 7 b'\xe6\x95\x99\xe8\x82\xb2'
f.close()
3.5 Python readline()和readlines()方法:按行读取文件
在 3.4 小节 中讲到,如果想读取用 open() 函数打开的文件中的内容,除了可以使用 read() 方法,还可以使用 readline() 和 readlines() 方法。和 read() 方法不同,这 2 个函数都以 "行" 作为读取单位,即每次都读取目标文件中的一行。对于读取以文本格式打开的文件,读取一行很好理解;对于读取以二进制格式打开的文件,它们会以 "\n" 作为读取一行的标志。
readline() 方法用于读取文件中的一行,包含最后的换行符 "\n"。此函数的基本语法格式为:
In [4]: f.readline?
Signature: f.readline(size=-1, /)
Docstring:
Read until newline or EOF.
Return an empty string if EOF is hit immediately.
If size is specified, at most size characters will be read.
Type: builtin_function_or_method
其中,f 为打开的文件对象;size 为可选参数,用于指定读取每一行时,一次最多读取的字符(字节)数。和 read() 函数一样,此函数成功读取文件数据的前提是,使用 open() 函数指定打开文件的模式必须为可读模式(包括 r、rb、r+、rb+ 4 种)。仍以 3.4 小节 中创建的 test.txt 文件为例,该文件中有如下 2 行数据:
100天精通Python
https://blog.youkuaiyun.com/xw1680/category_12955592.html
示例:
# -*- coding: utf-8 -*-
# @Time : 2025-05-13 8:40
# @Author : AmoXiang
# @File : 4.readline()与readlines()方法的使用.py
# @Software: PyCharm
# @Blog: https://blog.youkuaiyun.com/xw1680
f = open('test.txt', encoding='utf-8')
# 读取一行数据
row = f.readline()
print(row)
程序执行结果为:

由于 readline() 函数在读取文件中一行的内容时,会读取最后的换行符 "\n",再加上 print() 函数输出内容时默认会换行,所以输出结果中会看到多出了一个空行。不仅如此,在逐行读取时,还可以限制最多可以读取的字符(字节)数,例如:
f = open('test.txt', 'rb')
# 读取一行数据
row = f.readline(6)
print(row) # b'100\xe5\xa4\xa9'
print(row.decode('utf-8')) # 100天
运行结果为:

和上一个例子的输出结果相比,由于这里没有完整读取一行的数据,因此不会读取到换行符。
readlines() 方法用于读取文件中的所有行,它和调用不指定 size 参数的 read() 方法类似,只不过该函数返回是一个字符串列表,其中每个元素为文件中的一行内容。和 readline() 方法一样,readlines() 方法在读取每一行时,会连同行尾的换行符一块读取。readlines() 方法的基本语法格式如下:
In [5]: f.readlines?
Signature: f.readlines(hint=-1, /)
Docstring:
Return a list of lines from the stream.
hint can be specified to control the number of lines read: no more
lines will be read if the total size (in bytes/characters) of all
lines so far exceeds hint.
Type: builtin_function_or_method
其中,f 为打开的文件对象。和 read()、readline() 函数一样,它要求打开文件的模式必须为可读模式(包括 r、rb、r+、rb+ 4 种)。示例:
# -*- coding: utf-8 -*-
# @Time : 2025-05-13 8:40
# @Author : AmoXiang
# @File : 4.readline()与readlines()方法的使用.py
# @Software: PyCharm
# @Blog: https://blog.youkuaiyun.com/xw1680
f = open('test.txt', encoding='utf-8')
rows = f.readlines()
'''
['100天精通Python\n', 'https://blog.youkuaiyun.com/xw1680/category_12955592.html']
'''
print(rows)
f.close()
3.6 Python write()和writelines():向文件中写入数据
Python 中的文件对象提供了 write() 方法,可以向文件中写入指定内容。该方法的语法格式如下:
In [6]: f.write?
Signature: f.write(text, /)
Docstring:
Write string s to stream.
Return the number of characters written
(which is always equal to the length of the string).
Type: builtin_function_or_method
其中,f 表示已经打开的文件对象;text 表示要写入文件的字符串(或字节串,仅适用写入二进制文件中)。注意,在使用 write() 向文件中写入数据,需保证使用 open() 函数是以 r+、w、w+、a 或 a+ 的模式打开文件,否则执行 write() 函数会抛出 io.UnsupportedOperation 错误。例如,创建一个 write_demo.txt 文件,该文件内容如下:
100天精通Python
https://blog.youkuaiyun.com/xw1680/category_12955592.html
然后,在和 write_demo.txt 文件同级目录下,创建一个 Python 文件,编写如下代码:
# -*- coding: utf-8 -*-
# @Time : 2025-05-13 7:58
# @Author : AmoXiang
# @File : 4.write_demo.py
# @Software: PyCharm
# @Blog: https://blog.youkuaiyun.com/xw1680
f = open('write_demo.txt', 'w', encoding='utf-8')
f.write('你好,write()方法!!!')
f.close()
前面已经讲过,如果打开文件模式中包含 w(写入),那么向文件中写入内容时,会先清空原文件中的内容,然后再写入新的内容。因此运行上面程序,再次打开 write_demo.txt 文件,只会看到新写入的内容:

而如果打开文件模式中包含 a(追加),则不会清空原有内容,而是将新写入的内容会添加到原内容后边。例如,还原 write_demo.txt 文件中的内容,并修改上面代码为:
f = open('write_demo.txt', 'a', encoding='utf-8')
f.write('\n你好,write()方法!!!')
f.close()
再次打开 write_demo.txt,可以看到如下内容:

因此,采用不同的文件打开模式,会直接影响 write() 函数向文件中写入数据的效果。另外,在写入文件完成后,一定要调用 close() 函数将打开的文件关闭,否则写入的内容不会保存到文件中。此处演示使用 Pycharm 看不出来效果,打开 cmd 窗口,输入下面的代码:
C:\Users\amoxiang\Desktop>python3
C:\Users\amoxiang\Desktop>python
Python 3.12.8 (tags/v3.12.8:2dc476b, Dec 3 2024, 19:30:04) [MSC v.1942 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> f = open('test.txt', 'w', encoding='utf-8')
>>> f.write('我是一只小小鸟')
7
你会发现该 test.txt 文件是空的。这是因为,当我们在写入文件内容时,操作系统不会立刻把数据写入磁盘,而是先缓存起来,只有调用 close() 函数时,操作系统才会保证把没有写入的数据全部写入磁盘文件中。除此之外,如果向文件写入数据后,不想马上关闭文件,也可以调用文件对象提供的 flush() 函数,它可以实现将缓冲区的数据写入文件中。例如:
f = open('write_demo.txt', 'w', encoding='utf-8')
f.write('写入一行新数据')
# 打开 write_demo.txt 文件,可以看到写入的新内容: 写入一行新数据
f.flush()
拓展: 在 PyCharm 中没调用 close() 或 flush(),写入数据依然成功,很可能是因为:
- 数据量小:缓冲区还没来得及
"拖延写入"。操作系统的文件写入有缓冲机制,但如果写入内容很小(比如几十个字节),操作系统有时会直接写入磁盘(或者缓冲区刚好满了,也会自动 flush)。所以即使你没手动调用 close() 或 flush(),在小文件写入中"看起来"没问题。 - Python 解释器进程退出时会自动调用 close()。如果你用的是 PyCharm 或正常运行 Python 脚本,程序运行结束时:Python 的 GC(垃圾回收器)会销毁 f 对象;此时会自动调用
f.__del__()→ 最终调用 f.close();所以缓冲区被自动刷新,文件成功写入磁盘。⚠️ 但这并不是可靠行为!Python 的垃圾回收时机在复杂场景下并不保证立即执行(例如多线程、异常中断、解释器崩溃等)。 - PyCharm 的运行环境可能会
"保护性地清理资源"。PyCharm 是智能 IDE,它运行 Python 时可能添加了保护钩子,确保你运行脚本结束后文件被关闭(不代表普通 Python 环境也这样做)。
所以在 PyCharm 中,"看上去一切都正常",但你不能依赖它的行为在其他环境中也成立。总结:你观察到的写入成功现象属于 "侥幸" 行为。
| 条件 | 是否安全可靠? | 原因 |
|---|---|---|
| 写数据少,程序正常结束 | ✅ 一般会写成功 | OS 可能自动 flush,或 Python 退出自动 close |
| 写数据多,程序中途崩溃 | ❌ 有可能丢失数据 | 缓冲区没 flush,文件可能为空 |
| 忘记 close 而直接依赖自动机制 | ❌ 非推荐 | 不稳定,不可控,容易踩坑 |
有人可能会想到,通过设置 open() 函数的 buffering 参数可以关闭缓冲区,这样数据不就可以直接写入文件中了?对于以二进制格式打开的文件,可以不使用缓冲区,写入的数据会直接进入磁盘文件;但对于以文本格式打开的文件,必须使用缓冲区,否则 Python 解释器会 ValueError 错误。例如:
f = open('write_demo.txt', 'w', encoding='utf-8', buffering=0)
# ValueError: can't have unbuffered text I/O
f.write('写入一行新数据')
Python 的文件对象中,不仅提供了 write() 方法,还提供了 writelines() 方法,可以实现将字符串列表写入文件中。注意,写入方法只有 write() 和 writelines() 方法,而没有名为 writeline 的方法。例如,还是以 write_demo.txt 文件为例,通过使用 writelines() 方法,可以轻松实现将 write_demo.txt 文件中的数据复制到其它文件中,实现代码如下:
f1 = open('write_demo.txt', 'r', encoding='utf-8')
f2 = open('write_demo_bak.txt', 'w+', encoding='utf-8')
f2.writelines(f1.readlines())
f2.close()
f1.close()
执行此代码,在 write_demo.txt 文件同级目录下会生成一个 write_demo_bak.txt 文件,且该文件中包含的数据和 write_demo.txt 完全一样。需要注意的是,使用 writelines() 函数向文件中写入多行数据时,不会自动给各行添加换行符。上面例子中,之所以 write_demo_bak.txt 文件中会逐行显示数据,是因为 readlines() 函数在读取各行数据时,读入了行尾的换行符。
3.7 Python close()函数:关闭文件
在前面的小节中,对于使用 open() 函数打开的文件,我们一直都在用 close() 方法将其手动关闭。本小节就来详细介绍一下 close() 方法。close() 方法是专门用来关闭已打开文件的,其语法格式也很简单,如下所示:
In [2]: f.close?
Signature: f.close()
Docstring:
Flush and close the IO object.
This method has no effect if the file is already closed.
Type: builtin_function_or_method
其中,f 表示已打开的文件对象。读者可能一直存在这样的疑问,即使用 open() 函数打开的文件,在操作完成之后,一定要调用 close() 函数将其关闭吗?答案是肯定的。文件在打开并操作完成之后,就应该及时关闭,否则程序的运行可能出现问题。举个例子,分析如下代码:
C:\Users\amoxiang\Desktop>ipython
Python 3.12.8 (tags/v3.12.8:2dc476b, Dec 3 2024, 19:30:04) [MSC v.1942 64 bit (AMD64)]
Type 'copyright', 'credits' or 'license' for more information
IPython 8.32.0 -- An enhanced Interactive Python. Type '?' for help.
In [1]: f = open('test.txt', 'w', encoding='utf-8')
# .....
In [3]: import os
In [4]: os.remove('test.txt')
---------------------------------------------------------------------------
PermissionError Traceback (most recent call last)
Cell In[4], line 1
----> 1 os.remove('test.txt')
PermissionError: [WinError 32] 另一个程序正在使用此文件,进程无法访问。: 'test.txt'
代码中,我们引入了 os 模块,调用了该模块中的 remove() 函数,该函数的功能是删除指定的文件。发现运行此程序,Python 解释器报错。显然,由于我们使用了 open() 函数打开了 test.txt 文件,但没有及时关闭,直接导致后续的 remove() 函数运行出现错误。因此,正确的程序应该是这样的:
In [1]: f = open('test.txt', 'w', encoding='utf-8')
In [3]: import os
In [5]: f.close()
# 发现解释器不在报错,test.txt文件从桌面上被删除掉
In [6]: os.remove('test.txt')
关于写入的问题在 《3.6 Python write()和writelines():向文件中写入数据》 小节中已经演示过,这里不在赘述。关于文件描述符,资源释放的问题,也在 《二、文件读写实现原理与操作步骤》 中进行了讲解,这里也不再进行赘述。
3.8 Python seek()和tell()方法详解
在讲解 seek() 方法和 tell() 方法之前,首先来了解一下什么是文件指针。我们知道,使用 open() 函数打开文件并读取文件中的内容时,总是会从文件的第一个字符(字节)开始读起。那么,有没有办法可以自定指定读取的起始位置呢?答案是肯定,这就需要移动文件指针的位置。
文件指针用于标明文件读写的起始位置。假如把文件看成一个水流,文件中每个数据(以 b 模式打开,每个数据就是一个字节;以普通模式打开,每个数据就是一个字符)就相当于一个水滴,而文件指针就标明了文件将要从文件的哪个位置开始读起。下图简单示意了文件指针的概念:

可以看到,通过移动文件指针的位置,再借助 read() 和 write() 方法,就可以轻松实现,读取文件中指定位置的数据(或者向文件中的指定位置写入数据)。注意,当向文件中写入数据时,如果不是文件的尾部,写入位置的原有数据不会自行向后移动,新写入的数据会将文件中处于该位置的数据直接覆盖掉。实现对文件指针的移动,文件对象提供了 tell() 方法和 seek() 方法。tell() 方法用于判断文件指针当前所处的位置,而 seek() 方法用于移动文件指针到文件的指定位置。tell() 方法的用法很简单,其基本语法格式如下:
In [11]: f.tell?
Signature: f.tell()
Docstring:
Return the stream position as an opaque number.
The return value of tell() can be given as input to seek(), to restore a
previous stream position.
Type: builtin_function_or_method
其中,f 表示文件对象。例如,在同一目录下,编写如下程序对 a.txt 文件做读取操作,a.txt 文件中内容为:
https://blog.csdn.net/xw1680/category_12955592.html
读取 a.txt 的代码如下:
# -*- coding: utf-8 -*-
# @Time : 2025-05-13 10:38
# @Author : AmoXiang
# @File : 5.tell()方法.py
# @Software: PyCharm
# @Blog: https://blog.youkuaiyun.com/xw1680
f = open('a.txt', 'r', encoding='utf-8')
print(f.tell())
print(f.read(3))
print(f.tell())
'''
0
htt
3
'''
可以看到,当使用 open() 函数打开文件时,文件指针的起始位置为 0,表示位于文件的开头处,当使用 read() 方法从文件中读取 3 个字符之后,文件指针同时向后移动了 3 个字符的位置。这就表明,当程序使用文件对象读写数据时,文件指针会自动向后移动:读写了多少个数据,文件指针就自动向后移动多少个位置。seek() 方法用于将文件指针移动至指定位置,该函数的语法格式如下:
@abstractmethod
def seek(self, offset: int, whence: int = 0) -> int:
pass
f.seek()
其中,各个参数的含义如下:
- f:表示文件对象
- whence:作为可选参数,用于指定文件指针要放置的位置,该参数的参数值有 3 个选择:0 代表文件头(默认值)、1 代表当前位置、2 代表文件尾。
- offset:表示相对于 whence 位置文件指针的偏移量,正数表示向后偏移,负数表示向前偏移。例如,当
whence == 0 &&offset == 3(即 seek(3,0) ),表示文件指针移动至距离文件开头处 3 个字符的位置;当whence == 1 &&offset == 5(即 seek(5,1) ),表示文件指针向后移动,移动至距离当前位置 5 个字符处。 - 注意,当 offset 值非 0 时,Python 要求文件必须要以二进制格式打开,否则会抛出 io.UnsupportedOperation 错误。
示例:
# -*- coding: utf-8 -*-
# @Time : 2025-05-13 10:47
# @Author : AmoXiang
# @File : 6.seek()方法.py
# @Software: PyCharm
# @Blog: https://blog.youkuaiyun.com/xw1680
f = open('a.txt', 'rb')
'''
a.txt内容:
https://blog.youkuaiyun.com/xw1680/category_12955592.html
'''
# 判断文件指针的位置
print(f.tell()) # 0
# 读取一个字节,文件指针自动后移1个数据
print(f.read(1)) # b'h'
print(f.tell()) # 1
# 将文件指针从文件开头,向后移动到 5 个字符的位置
f.seek(5)
print(f.tell()) # 5
print(f.read(1)) # b':'
print(f.tell()) # 6
# 将文件指针从当前位置,向后移动到 5 个字符的位置
f.seek(5, 1)
print(f.tell()) # 11
print(f.read(1)) # b'g'
# 将文件指针从文件结尾,向前移动到距离 2 个字符的位置
f.seek(-1, 2) #
print(f.tell()) # 50
print(f.read(1)) # b'l'
注意:由于程序中使用 seek() 时,使用了非 0 的偏移量,因此文件的打开方式中必须包含 b,否则就会报 io.UnsupportedOperation: can't do nonzero end-relative seeks 错误,有兴趣可以自己测试。上面程序示范了使用 seek() 方法来移动文件指针,包括从文件开头、指针当前位置、文件结尾处开始计算。运行上面程序,结合程序输出结果可以体会文件指针移动的效果。由于文章篇幅有限,下面感兴趣的你们也可以自行去测试:
- 文本模式
- seek(0)、seek(200),相对开始向右偏移,不能左超界
- seek(0, 1),只能是偏移 0,就是原地踏步,没有用的操作
- seek(0, 2),只能是偏移 0,就是跳到 EOF
- 字节模式
- seek(0)、seek(200),相对开始向右偏移,不能左超界
- seek(0, 1)、seek(-5, 1)、seek(5, 1),相对当前索引位置偏移,不能左超界
- seek(0, 2)、seek(-5, 2)、seek(5, 2),相对 EOF 偏移,不能左超界
- 最常用的操作就是 seek(0)、seek(0, 2)
3.9 上下文管理
文件对象这种打开资源并一定要关闭的对象,为了保证其打开后一定关闭,为其提供了上下文支持。语法格式:
with 文件对象 as 标识符: # 等同于 标识符 = 文件对象
pass # 标识符可以在内部使用
上下文管理:
- 使用 with 关键字,上下文管理针对的是 with 后的对象
- 使用 with … as 关键字
- 上下文管理的语句块并不会开启新的作用域
文件对象上下文管理:
- 进入 with 时,with 后的文件对象是被管理对象
- as 子句后的标识符,指向 with 后的文件对象
- with 语句块执行完的时候,会自动关闭文件对象
示例代码:
# -*- coding: utf-8 -*-
# @Time : 2025-05-13 11:03
# @Author : AmoXiang
# @File : 7.上下文管理.py
# @Software: PyCharm
# @Blog: https://blog.youkuaiyun.com/xw1680
file_name = './a.txt'
with open(file_name, 'r') as f:
print(1, f.closed)
print(f.write('abcd')) # r模式写入失败,抛异常
print(2, f.closed) # with中不管是否抛异常,with结束时都会保证关闭文件对象
写入示例:
C:\Users\amoxiang\Desktop>python
Python 3.12.8 (tags/v3.12.8:2dc476b, Dec 3 2024, 19:30:04) [MSC v.1942 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
# 发现并未显示调用close()方法,但是桌面文件1.txt写入内容成功,可见with...as语句已经自动帮我们进行了处理
>>> with open('1.txt', 'w', encoding='utf-8') as file:
... file.write('hello, Amo!我是一只小小鸟~~~')
...
21
with as 语句实现的底层原理到底是怎样的呢?可以阅读 《100天精通Python——基础篇 2025 第14天:深入掌握魔术方法与元类,玩转高级OOP技巧》一文的 《八、魔术方法之上下文管理》 小节做详细了解。
3.10 文件的遍历
类似于日志文件,文件需要遍历,最常用的方式就是逐行遍历。示例代码:
# -*- coding: utf-8 -*-
# @Time : 2025-05-13 11:16
# @Author : AmoXiang
# @File : 8.文件的遍历.py
# @Software: PyCharm
# @Blog: https://blog.youkuaiyun.com/xw1680
filename = './b.txt'
with open(filename, 'w', encoding='utf-8') as f:
f.write('\n'.join(map(str, range(101, 120))))
with open(filename, 'r', encoding='utf-8') as f:
for line in f: # 文件对象时可迭代对象,逐行遍历
print(line.encode()) # 带换行符
3.11 路径操作
- 《Python 常用模块(三):os.path模块》 👉 查看文章
四、shutil模块
《Python 常用模块(四):shutil模块》 👉 查看文章
五、序列化和反序列化
5.1 为什么要序列化?
内存中的字典、列表、集合以及各种对象,如何保存到一个文件中?如果是自己定义的类的实例,如何保存到一个文件中?如何从文件中读取数据,并让它们在内存中再次恢复成自己对应的类的实例?要设计一套协议,按照某种规则,把内存中数据保存到文件中。文件是一个字节序列,所以必须把数据转换成字节序列,输出到文件。这就是序列化。 反之,从文件的字节序列恢复到内存并且还是原来的类型,就是反序列化。
5.2 定义
serialization 序列化:将内存中对象存储下来,把它变成一个个字节。 数据结构 → 二进制
deserialization 反序列化:将文件的一个个字节恢复成内存中对象。 二进制 → 数据结构
序列化保存到文件就是持久化。可以将数据序列化后持久化,或者网络传输;也可以将从文件中或者网络接收到的字节序列反序列化。Python 提供了 pickle 库。
5.3 pickle
Python 中的序列化、反序列化模块。
| 函数 | 说明 |
|---|---|
| dumps | 对象序列化为bytes对象 |
| dump | 对象序列化到文件对象,就是存入文件 |
| loads | 从bytes对象反序列化 |
| load | 对象反序列化,从文件读取数据 |
示例代码:
# -*- coding: utf-8 -*-
# @Time : 2025-05-13 11:38
# @Author : AmoXiang
# @File : 9.pickle_demo.py
# @Software: PyCharm
# @Blog: https://blog.youkuaiyun.com/xw1680
import pickle
filename = './ser'
# 序列化后看到什么
i = 99
c = 'c'
l = list('123')
d = {'a': 127, 'b': 'abc', 'c': [1, 2, 3]}
# 序列化
with open(filename, 'wb') as f:
pickle.dump(i, f)
pickle.dump(c, f)
pickle.dump(l, f)
pickle.dump(d, f)
# 反序列化
with open(filename, 'rb') as f:
print(f.read(), f.seek(0))
for i in range(4):
x = pickle.load(f)
print(i, x, type(x))
5.4 序列化应用
一般来说,本地序列化的情况,应用较少。大多数场景都应用在网络传输中。将数据序列化后通过网络传输到远程节点,远程服务器上的服务将接收到的数据反序列化后,就可以使用了。但是,要注意一点,远程接收端,反序列化时必须有对应的数据类型,否则就会报错。尤其是自定义类,必须远程得有一致的定义。现在,大多数项目,都不是单机的,也不是单服务的,需要多个程序之间配合。需要通过网络将数据传送到其他节点上去,这就需要大量的序列化、反序列化过程。但是,问题是,Python 程序之间可以都用 pickle 解决序列化、反序列化,如果是跨平台、跨语言、跨协议 pickle 就不太适合了,就需要公共的协议。例如 XML、Json、Protocol Buffer、msgpack 等。不同的协议,效率不同、学习曲线不同,适用不同场景,要根据不同的情况分析选型。
5.5 JSON
《Python 常用模块(二):json模块》 👉 查看文章
六、csv文件
《Python 常用模块(一):csv模块》 👉 查看文章
👉 下一天的学习: 点击阅读 《100天精通Python——基础篇 2025 第20天:正则表达式入门实战,解锁字符串处理的魔法力量》
至此今天的学习就到此结束了,本文为个人学习记录与复习整理之用,旨在帮助自己系统巩固Python相关知识,同时也希望能为正在学习该领域的同学提供一些参考与帮助。部分内容参考了公开课资料、他人学习笔记或网络公开资源,其中部分图片或示意图来自网络,仅用于非商业性质的学习交流。如有侵权或不当引用之处,敬请联系我删除或更正。感谢您阅读本篇博文,希望本文能成为您编程路上的领航者。祝您阅读愉快!

好书不厌读百回,熟读课思子自知。而我想要成为全场最靓的仔,就必须坚持通过学习来获取更多知识,用知识改变命运,用博客见证成长,用行动证明我在努力。
如果我的博客对你有帮助、如果你喜欢我的博客内容,请点赞、评论、收藏一键三连哦!听说点赞的人运气不会太差,每一天都会元气满满呦!如果实在要白嫖的话,那祝你开心每一天,欢迎常来我博客看看。
编码不易,大家的支持就是我坚持下去的动力。点赞后不要忘了关注我哦!

被折叠的 条评论
为什么被折叠?



