首页 > 编程笔记
Python文件操作教程(附带示例)
对于文件操作,Python 内置了 open() 方法,该方法可以对文件进行读写操作,使用 open() 方法操作文件分3步:打开文件、操作文件和关闭文件。
Python open() 方法的格式为:
open() 方法提供的打开模式如表1所示。
Python 中常用的读取和写入文件内容的方法,如表2所示。
打开文件后,Python 提供了多种方法读取文件:
我们应根据需要决定如何调用读取文件的方法。调用 read() 方法会一次性读取文件的全部内容,如果文件有 10GB,内存就承受不住了,所以保险起见,我们可以反复调用 read(size) 方法,每次最多读取 size 个字节的内容。
如果文件很小,调用 read() 方法一次性读取最方便;如果不能确定文件大小,反复调用 read(size) 比较保险;如果是配置文件,调用 readlines() 方法读取最方便。
文件读写相对比较简单,接下来,我们重点讲解一下 seek() 方法。
如果我们希望指定读取的起始位置,就需要移动文件指针的位置。seek() 方法用于将文件指针移动至指定位置,其语法格式如下:
通过移动文件指针的位置,再借助 read() 方法和 write() 方法,我们可以轻松实现读取文件中指定位置的数据(或者向文件中的指定位置写入数据)。
注意,当向文件中写入数据时,如果不是文件的尾部,写入位置的原有数据不会自行向后移动,新写入的数据会直接覆盖文件中处于该位置的数据。
可以采用 with as 语句灵活地管理文件的关闭操作,我们可以不需要再写 close 语句,但要注意代码缩进。
这里推荐大家用 with open(),它可以让系统自动进行 IO 缓存和内存管理,不需要管系统如何分配这些内存,并且在读取完成之后,我们不需要使用 close() 关闭文件句柄。
另外,结合 for line in f,文件对象f将被视为一个迭代器,自动地采用缓冲 IO 和内存管理,所以我们可以不用担心大文件处理产生异常。
我们针对 rb 方式进行简单测试,遍历 100 万行内容基本在3秒内完成,能满足处理中大型文件的效率需求。
当下载的文件非常大,计算机的内存空间完全不够用的情况下,我们可以使用 requests 库的流模式:
其中,iter_content() 方法逐块遍历要下载的内容,iter_lines() 方法逐行遍历要下载的内容,使用这两个方法下载大文件可以防止占用过多的内存,因为这两个方法每次循环只下载小部分数据。
示例如代码如下所示:
代码优化后如下所示:
open() 的基本用法
在读写文件之前,我们首先使用 open() 方法打开文件,open() 方法的返回值是一个 file 对象,可以将它赋值给一个变量。Python open() 方法的格式为:
<变量名>=open(<文件名>,<打开模式>)
open() 有两个参数:文件名和打开模式。其中,文件名可以是单独的文件名称,也可以是包含完整路径的名称,在写文件名时需包含文件扩展名。open() 方法提供的打开模式如表1所示。
打开模式 | 描述 |
---|---|
r | 以只读方式打开文件。文件的指针将会放在文件的开头。这是默认模式。 |
rb | 以二进制格式打开一个文件,且用户只读。文件指针将会放在文件的开头。这是默认模式。 |
r+ | 打开一个文件用于读写。文件指针将会放在文件的开头。 |
rb+ | 以二进制格式打开一个文件用于读写。文件指针将会放在文件的开头。 |
w | 打开一个文件只用于写入。如果该文件已存在,则将其覆盖;如果文件不存在,则创建新文件。 |
wb | 以二进制格式打开一个文件只用于写入。如果该文件已存在,则将其覆盖;如果该文件不存在,则创建新文件。 |
w+ | 打开一个文件用于读写。如果该文件已存在,则将其覆盖;如果该文件不存在,则创建新文件。 |
wb+ | 以二进制格式打开一个文件用于读写。如果该文件已存在,则将其覆盖;如果该文件不存在,则创建新文件。 |
a | 打开一个文件用于追加内容。如果该文件已存在,文件指针将会在文件的结尾,即新的内容将被写入已有内容之后;如果该文件不存在,则创建新文件并写入。 |
ab | 以二进制格式打开一个文件用于追加内容。如果该文件已存在,文件指针将会在文件的结尾,即新的内容将会被写入已有内容,如果该文件不存在,创建新文件并写入。 |
a+ | 打开一个文件用于读写。如果该文件已存在,文件指针将会放在文件的结尾,文件打开时会是追加模式。如果该文件不存在,创建新文件并写入。 |
ab+ | 以二进制格式打开一个文件用于追加内容。如果该文件已存在,文件指针将会在文件的结尾;如果该文件不存在,创建新文件并写入。 |
提示
如果要读取非 UTF-8 编码的文件,需要给 open() 方法传入 encoding 参数。例如,读取 GBK 编码的文件:f = open('gbk.txt', 'r', encoding='gbk')如果遇到编码不规范的文件,程序可能会抛出 UnicodeDecodeError 异常,这表示文件中可能夹杂了一些非法编码的字符。遇到这种情况,我们可以使用 errors 参数,表示遇到编码错误后的处理方式:
f = open('gbk.txt', 'r', encoding='gbk', errors='ignore')
文件的读写
在使用 open() 方法打开文件后,我们可以根据打开方式的不同对文件进行相应的操作:- 如果文件以文本方式打开,文件的读写将按照字符串方式,采用当前计算机使用的编码或指定编码;
- 如果文件以二进制方式打开,文件的读写将按照字节流方式。
Python 中常用的读取和写入文件内容的方法,如表2所示。
方法 | 描述 |
---|---|
readall() | 读取整个文件的内容,返回一个字符串或字节流。 |
read(size=-1) |
读取整个文件的内容,如果设置参数 size,则读取长度为 size 的字符串或字节流,然后作为字符串或字节对象返回。 size 是一个可选的数字类型的参数,用于指定读取的数据量。当 size 被忽略或者为负值时,该文件的所有内容都将被读取并且返回。 |
readline(size=-1) |
从文件中读入一行内容,如果设置参数 size,则读取长度为 size 的字符串或字节流,换行符为\n 。如果返回一个空字符串,说明已经读取到最后一行。这个方法通常适用于读一行处理一行的场景。 |
readlines(size=-1) |
从文件中读入所有行,以每行为元素形成一个列表,如果设置参数 hint,则读入 hint 行,将文件中所有的行,逐行读入一个列表,按顺序逐个作为列表的元素,并返回这个列表。 readlines() 方法会一次性将文件全部读入内存,虽然这存在一定的弊端,但是它的好处是每行都保存在列表中,我们可随意存取。 |
write(s) | 将文件写入一个字符串或字节流。 |
writelines(lines) | 将一个元素全为字符串的列表写入文件。 |
seek(offset) |
改变当前文件操作指针的位置,offset 的值为 0 表示指针在文件开头;值为 1 表示指针在文件当前位置;值为 2 表示指针在文件末尾。 如果要改变位置指针的位置,可以使用 f.seek(offset,from_what) 方法。其中 from_what 的值为 0 表示从文件开头计算;值为 1 表示从文件读写指针的当前位置开始计算;值为 2 表示从文件的结尾开始计算。from_what 的值默认为 0。 例如 offset 表示偏移量;seek(x,0) 表示从起始位置即文件首行首字符开始移动 x 个字符;seek(x,1) 表示从当前位置往后移动 x 个字符;seek(-x,2) 表示从文件的结尾往前移动 x 个字符。 seek()方法经常和tell()方法配合使用。 |
tell() | 返回文件读写指针当前所处的位置,它是从文件开头算起的字节数。注意,是字节数,不是字符数。 |
打开文件后,Python 提供了多种方法读取文件:
- read() 方法用于读取整个文件,通常将文件内容放入一个字符串变量;
- readline() 方法用于每次读取一行内容;
- readlines() 方法用于一次性读取所有内容并按行返回列表。
我们应根据需要决定如何调用读取文件的方法。调用 read() 方法会一次性读取文件的全部内容,如果文件有 10GB,内存就承受不住了,所以保险起见,我们可以反复调用 read(size) 方法,每次最多读取 size 个字节的内容。
如果文件很小,调用 read() 方法一次性读取最方便;如果不能确定文件大小,反复调用 read(size) 比较保险;如果是配置文件,调用 readlines() 方法读取最方便。
文件读写相对比较简单,接下来,我们重点讲解一下 seek() 方法。
如果我们希望指定读取的起始位置,就需要移动文件指针的位置。seek() 方法用于将文件指针移动至指定位置,其语法格式如下:
file.seek(offset[,whence])
其中,各个参数的含义如下:- file 表示文件对象。
-
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.Unsupported Operation 错误。
提示
文件指针用于标明文件读写的起始位置:- 如果以 b 模式打开,每个数据就是 1 字节;
- 如果以普通模式打开,每个数据就是一个字符。
通过移动文件指针的位置,再借助 read() 方法和 write() 方法,我们可以轻松实现读取文件中指定位置的数据(或者向文件中的指定位置写入数据)。
注意,当向文件中写入数据时,如果不是文件的尾部,写入位置的原有数据不会自行向后移动,新写入的数据会直接覆盖文件中处于该位置的数据。
文件的关闭
当处理完一个文件后,调用 f.close() 方法来关闭文件并释放系统的资源。文件关闭后,如果尝试再次调用该文件对象,则会抛出异常。可以采用 with as 语句灵活地管理文件的关闭操作,我们可以不需要再写 close 语句,但要注意代码缩进。
大文件处理
小文件的处理通常不会出现问题,而大文件的处理,稍有不慎或者处理不好将会报错或者使得性能变差。例如,文件约 4GB,在处理文本文档时,经常会出现 memoryError 错误和文件读取太慢的问题。这里推荐大家用 with open(),它可以让系统自动进行 IO 缓存和内存管理,不需要管系统如何分配这些内存,并且在读取完成之后,我们不需要使用 close() 关闭文件句柄。
另外,结合 for line in f,文件对象f将被视为一个迭代器,自动地采用缓冲 IO 和内存管理,所以我们可以不用担心大文件处理产生异常。
with open(...) as f: for line in f: process(line) # <do something with line>面对百万行的大型数据使用 with open() 是没有问题的,但是这里面参数的不同会导致效率的不同。这里建议大家使用参数
rb
,二进制读取依然是最快的模式,即 with open(filename,"rb")as f。我们针对 rb 方式进行简单测试,遍历 100 万行内容基本在3秒内完成,能满足处理中大型文件的效率需求。
分块下载大文件
Python 下载文件的方式有很多,其中 requests 库非常简洁,从下载简单的小文件到用断点续传的方式下载大文件都支持。当下载的文件非常大,计算机的内存空间完全不够用的情况下,我们可以使用 requests 库的流模式:
- 在默认情况下,stream 参数值为 False,会因文件过大导致内存不足。
- 当 stream 参数值为 True 时,requests 库并不会立刻开始下载,只有在调用 iter_content() 方法或者 iter_lines() 方法遍历内容时下载。
其中,iter_content() 方法逐块遍历要下载的内容,iter_lines() 方法逐行遍历要下载的内容,使用这两个方法下载大文件可以防止占用过多的内存,因为这两个方法每次循环只下载小部分数据。
示例如代码如下所示:
# -*- coding: utf-8 -*- # @Time : 2023/7/26 11:29 上午 # @Project : fileDemo # @File : downloadFile1.py # @Version: Python3.9.8 import requests def steam_download(url): with requests.get(url, stream=True) as r: with open('vscode.exe', 'wb') as flie: # chunk_size指定写入大小,每次写入1024*1024字节 for chunk in r.iter_content(chunk_size=1024*1024): if chunk: flie.write(chunk)在下载大文件的时候,我们可以加上进度条美化下载界面,实时获取下载的网络速度和已经下载的文件大小。这里使用 tqdm 库作为进度条显示,具体的 tqdm 库参数的含义,大家可自行查找。
代码优化后如下所示:
# -*- coding: utf-8 -*- # @Time : 2023/7/26 11:45 上午 # @Project : fileDemo # @File : downloadFile2.py # @Version: Python3.9.8 from tqdm import tqdm def tqdm_download(url): resp = requests.get(url, stream=True) # 获取文件大小 file_size = int(resp.headers['content-length']) with tqdm(total=file_size, unit='B', unit_scale=True, unit_divisor=1024, ascii=True, desc='vscode.exe') as bar: with requests.get(url, stream=True) as r: with open('vscode.exe', 'wb') as fp: for chunk in r.iter_content(chunk_size=512): if chunk: fp.write(chunk) bar.update(len(chunk))