首页 > 编程笔记

Python文件操作教程(附带示例)

对于文件操作,Python 内置了 open() 方法,该方法可以对文件进行读写操作,使用 open() 方法操作文件分3步:打开文件、操作文件和关闭文件。

open() 的基本用法

在读写文件之前,我们首先使用 open() 方法打开文件,open() 方法的返回值是一个 file 对象,可以将它赋值给一个变量。

Python open() 方法的格式为:

<变量名>=open(<文件名>,<打开模式>)

open() 有两个参数:文件名和打开模式。其中,文件名可以是单独的文件名称,也可以是包含完整路径的名称,在写文件名时需包含文件扩展名。

open() 方法提供的打开模式如表1所示。

表1:open() 方法提供的打开模式
打开模式 描述
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所示。

表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() 方法会一次性读取文件的全部内容,如果文件有 10GB,内存就承受不住了,所以保险起见,我们可以反复调用 read(size) 方法,每次最多读取 size 个字节的内容。

如果文件很小,调用 read() 方法一次性读取最方便;如果不能确定文件大小,反复调用 read(size) 比较保险;如果是配置文件,调用 readlines() 方法读取最方便。

文件读写相对比较简单,接下来,我们重点讲解一下 seek() 方法。

如果我们希望指定读取的起始位置,就需要移动文件指针的位置。seek() 方法用于将文件指针移动至指定位置,其语法格式如下:

file.seek(offset[,whence])

其中,各个参数的含义如下:

注意,当 offset 值非 0 时,Python 要求文件必须要以二进制格式打开,否则将抛出 io.Unsupported Operation 错误。

提示

文件指针用于标明文件读写的起始位置:
通过移动文件指针的位置,再借助 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 库的流模式:
其中,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))

推荐阅读