Python文件操作(I/O)

Python文件操作(I/O)和其它编程语言一样,Python 也具有操作文件(I/O)的能力,比如打开文件、读取和追加数据、插入和删除数据、关闭文件、删除文件等。

除了提供文件操作基本的函数之外,Python 还提供了很多模块,例如 fileinput 模块、pathlib 模块等,通过引入这些模块,我们可以获得大量实现文件操作可用的函数和方法(类属性和类方法),大大提供编写代码的效率。

1. 什么是文件路径,Python中如何书写文件路径?

当程序运行时,变量是保存数据的好方法,但变量、序列以及对象中存储的数据是暂时的,程序结束后就会丢失,如果希望程序结束后数据仍然保持,就需要将数据保存到文件中。Python 提供了内置的文件对象,以及对文件、目录进行操作的内置模块,通过这些技术可以很方便地将数据保存到文件(如文本文件等)中。

关于文件,它有两个关键属性,分别是“文件名”和“路径”。其中,文件名指的是为每个文件设定的名称,而路径则用来指明文件在计算机上的位置。例如,我的 Windows 7 笔记本上有一个文件名为 projects.docx(句点之后的部分称为文件的“扩展名”,它指出了文件的类型),它的路径在 D:\demo\exercise,也就是说,该文件位于 D 盘下 demo 文件夹中 exercise 子文件夹下。

通过文件名和路径可以分析出,project.docx 是一个 Word 文档,demo 和 exercise 都是指“文件夹”(也称为目录)。文件夹可以包含文件和其他文件夹,例如 project.docx 在 exercise 文件夹中,该文件夹又在 demo 文件夹中。

注意,路径中的 D:\ 指的是“根文件夹”,它包含了所有其他文件夹。在 Windows 中,根文件夹名为 D:\,也称为 D: 盘。在 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() 就会返回一个文件路径的字符串,包含正确的路径分隔符。在交互式环境中输入以下代码:

>>> import os
>>> os.path.join('demo', 'exercise')
'demo\\exercise'

因为此程序是在 Windows 上运行,所以 os.path.join('demo', 'exercise') 返回 'demo\\exercise'(请注意,反斜杠有两个,因为每个反斜杠需要由另一个反斜杠字符来转义)。如果在 OS X 或 Linux 上调用这个函数,该字符串就会是 'demo/exercise'

不仅如此,如果需要创建带有文件名称的文件存储路径,os.path.join() 函数同样很有用。例如,下面的例子将一个文件名列表中的名称,添加到文件夹名称的末尾:

import os
myFiles = ['accounts.txt', 'details.csv', 'invite.docx']
for filename in myFiles:
    print(os.path.join('C:\\demo\\exercise', filename))

运行结果为:

C:\demo\exercise\accounts.txt
C:\demo\exercise\details.csv
C:\demo\exercise\invite.docx

2. Python绝对路径和相对路径详解

在介绍绝对路径和相对路径之前,先要了解一下什么是当前工作目录。每个运行在计算机上的程序,都有一个“当前工作目录”(或 cwd)。所有没有从根文件夹开始的文件名或路径,都假定在当前工作目录下。注意,虽然文件夹是目录的更新颖的名称(文件夹=目录),但当前工作目录(或当前目录)是标准术语,没有当前工作文件夹这种说法

在 Python 中,利用 os.getcwd() 函数可以取得当前工作路径的字符串,还可以利用 os.chdir() 改变它。例如,在交互式环境中输入以下代码:

>>> import os
>>> os.getcwd()
'C:\\Users\\mengma\\Desktop'
>>> os.chdir('C:\\Windows\\System32')
>>> os.getcwd()
'C:\\Windows\\System32'

可以看到,原本当前工作路径为 'C:\\Users\\mengma\\Desktop'(也就是桌面),通过 os.chdir() 函数,将其改成了 'C:\\Windows\\System32'

需要注意的是,如果使用 os.chdir() 修改的工作目录不存在,Python 解释器会报错,例如:

>>> os.chdir('C:\\error')
Traceback (most recent call last):
  File "<pyshell#6>", line 1, in <module>
    os.chdir('C:\\error')
FileNotFoundError: [WinError 2] 系统找不到指定的文件。: 'C:\\error'

了解了当前工作目录的具体含义之后,接下来介绍绝对路径和相对路径各自的含义和用法。

1.1 什么是绝对路径与相对路径

明确一个文件所在的路径,有 2 种表示方式,分别是:

  • 绝对路径

总是从根文件夹开始,Window 系统中以盘符(C: D:)作为根文件夹,而 OS X 或者 Linux 系统中以 / 作为根文件夹。

  • 相对路径

指的是文件相对于当前工作目录所在的位置。例如,当前工作目录为 "C:\Windows\System32",若文件 demo.txt 就位于这个 System32 文件夹下,则 demo.txt 的相对路径表示为 ".\demo.txt"(其中 .\ 就表示当前所在目录)。

在使用相对路径表示某文件所在的位置时,除了经常使用 .\ 表示当前所在目录之外,还会用到 ..\ 表示当前所在目录的父目录
在这里插入图片描述
以上图为例,如果当前工作目录设置为 C:\bacon,则这些文件夹和文件的相对路径和绝对路径,就对应为该图右侧所示的样子。

1.2 Python处理绝对路径和相对路径

Python os.path 模块提供了一些函数,可以实现绝对路径和相对路径之间的转换,以及检查给定的路径是否为绝对路径,比如说:

(1)调用 os.path.abspath(path)返回 path 参数的绝对路径的字符串,这是将相对路径转换为绝对路径的简便方法。

(2)调用 os.path.isabs(path),如果参数是一个绝对路径,就返回 True,如果参数是一个相对路径,就返回 False

(3)调用 os.path.relpath(path, start) 将返回从 start 路径到 path 的相对路径的字符串。如果没有提供 start,就使用当前工作目录作为开始路径。

(4)调用 os.path.dirname(path) 将返回一个字符串,它包含 path 参数中最后一个斜杠之前的所有内容

(5)调用 os.path.basename(path) 将返回一个字符串,它包含 path 参数中最后一个斜杠之后的所有内容

在交互式环境中尝试上面提到的函数:

>>> os.getcwd()
'C:\\Windows\\System32'
>>> os.path.abspath('.')
'C:\\Windows\\System32'
>>> os.path.abspath('.\\Scripts')
'C:\\Windows\\System32\\Scripts'
>>> os.path.isabs('.')
False
>>> os.path.isabs(os.path.abspath('.'))
True
>>> os.path.relpath('C:\\Windows', 'C:\\')
'Windows'
>>> os.path.relpath('C:\\Windows', 'C:\\spam\\eggs')
'..\\..\\Windows'
>>> path = 'C:\\Windows\\System32\\calc.exe'
>>> os.path.basename(path)
'calc.exe'
>>> os.path.dirname(path)
'C:\\Windows\\System32'

注意,由于读者的系统文件和文件夹可能与我的不同,所以读者不必完全遵照本节的例子,根据自己的系统环境对本节代码做适当调整即可。

除此之外,如果同时需要一个路径的目录名称和基本名称,就可以调用 os.path.split() 获得这两个字符串的元组,例如:

>>> path = 'C:\\Windows\\System32\\calc.exe'
>>> os.path.split(path)
('C:\\Windows\\System32', 'calc.exe')

注意,可以调用 os.path.dirname()os.path.basename(),将它们的返回值放在一个元组中,从而得到同样的元组。但使用 os.path.split() 无疑是很好的快捷方式。

同时,如果提供的路径不存在,许多 Python 函数就会崩溃并报错,但好在 os.path 模块提供了以下函数用于检测给定的路径是否存在,以及它是文件还是文件夹:
(1)如果 path 参数所指的文件或文件夹存在,调用os.path.exists(path) 将返回 True,否则返回 False
(2)如果 path 参数存在,并且是一个文件,调用 os.path.isfile(path) 将返回 True,否则返回 False
(3)如果 path 参数存在,并且是一个文件夹,调用 os.path.isdir(path) 将返回 True,否则返回 False

下面是在交互式环境中尝试这些函数的结果:

>>> os.path.exists('C:\\Windows')
True
>>> os.path.exists('C:\\some_made_up_folder')
False
>>> os.path.isdir('C:\\Windows\\System32')
True
>>> os.path.isfile('C:\\Windows\\System32')
False
>>> os.path.isdir('C:\\Windows\\System32\\calc.exe')
False
>>> os.path.isfile('C:\\Windows\\System32\\calc.exe')
True

3.Python文件基本操作(入门必读)

Python 中,对文件的操作有很多种,常见的操作包括创建、删除、修改权限、读取、写入等,这些操作可大致分为以下 2 类:

  • 删除、修改权限:作用于文件本身,属于系统级操作。
  • 写入、读取:是文件最常用的操作,作用于文件的内容,属于应用级操作。

其中,对文件的系统级操作功能单一,比较容易实现,可以借助 Python 中的专用模块(ossys 等),并调用模块中的指定函数来实现。例如,假设如下代码文件的同级目录中有一个文件“a.txt”,通过调用 os 模块中的 remove 函数,可以将该文件删除,具体实现代码如下:

import os
os.remove("a.txt")

而对于文件的应用级操作,通常需要按照固定的步骤进行操作,且实现过程相对比较复杂,同时也是本章重点要讲解的部分。

文件的应用级操作可以分为以下 3 步,每一步都需要借助对应的函数实现:

  1. 打开文件:使用 open() 函数,该函数会返回一个文件对象
  2. 对已打开文件做读/写操作:读取文件内容可使用 read()readline() 以及 readlines() 函数;向文件中写入内容,可以使用 write() 函数。
  3. 关闭文件:完成对文件的读/写操作之后,最后需要关闭文件,可以使用 close() 函数。

一个文件,必须在打开之后才能对其进行操作,并且在操作结束之后,还应该将其关闭,这 3 步的顺序不能打乱。

4.Python open()函数详解:打开指定文件

在 Python 中,如果想要操作文件,首先需要创建或者打开指定的文件,并创建一个文件对象,而这些工作可以通过内置的 open() 函数实现。

open() 函数用于创建或打开指定文件,该函数的常用语法格式如下:

file = open(file_name [, mode='r' [ , buffering=-1 [ , encoding = None ]]])

此格式中,用 [] 括起来的部分为可选参数,既可以使用也可以省略。其中,各个参数所代表的含义如下:

  • file:表示要创建的文件对象。
  • file_mode:要创建或打开文件的文件名称,该名称要用引号(单引号或双引号都可以)括起来。需要注意的是,如果要打开的文件和当前执行的代码文件位于同一目录,则直接写文件名即可;否则,此参数需要指定打开文件所在的完整路径。
  • mode:可选参数,用于指定文件的打开模式。可选的打开模式如表 1 所示。如果不写,则默认以只读(r)模式打开文件。
  • buffing:可选参数,用于指定对文件做读写操作时,是否使用缓冲区(本节后续会详细介绍)。
  • encoding:手动设定打开文件时所使用的编码格式,不同平台的 ecoding 参数值也不同,以 Windows 为例,其默认为
    cp936(实际上就是 GBK 编码)。

open() 函数支持的文件打开模式如表 1 所示。

表 1 open 函数支持的文件打开模式
模式 意义 注意事项
r 只读模式打开文件,读文件内容的指针会放在文件的开头。 操作的文件必须存在。
rb 以二进制格式、采用只读模式打开文件,读文件内容的指针位于文件的开头,一般用于非文本文件,如图片文件、音频文件等。
r+ 打开文件后,既可以从头读取文件内容,也可以从开头向文件中写入新的内容,写入的新内容会覆盖文件中等长度的原有内容。
rb+ 以二进制格式、采用读写模式打开文件,读写文件的指针会放在文件的开头,通常针对非文本文件(如音频文件)。
w 以只写模式打开文件,若该文件存在,打开时会清空文件中原有的内容。 若文件存在,会清空其原有内容(覆盖文件);反之,则创建新文件。
wb 以二进制格式、只写模式打开文件,一般用于非文本文件(如音频文件)
w+ 打开文件后,会对原有内容进行清空,并对该文件有读写权限。
wb+ 以二进制格式、读写模式打开文件,一般用于非文本文件
a 以追加模式打开一个文件,对文件只有写入权限,如果文件已经存在,文件指针将放在文件的末尾(即新写入内容会位于已有内容之后);反之,则会创建新文件。  
ab 以二进制格式打开文件,并采用追加模式,对文件只有写权限。如果该文件已存在,文件指针位于文件末尾(新写入文件会位于已有内容之后);反之,则创建新文件。  
a+ 以读写模式打开文件;如果文件存在,文件指针放在文件的末尾(新写入文件会位于已有内容之后);反之,则创建新文件。  
ab+ 以二进制模式打开文件,并采用追加模式,对文件具有读写权限,如果文件存在,则文件指针位于文件的末尾(新写入文件会位于已有内容之后);反之,则创建新文件。

文件打开模式,直接决定了后续可以对文件做哪些操作。例如,使用 r 模式打开的文件,后续编写的代码只能读取文件,而无法修改文件内容。

下图 将以上几个容易混淆的文件打开模式的功能做了对比:
在这里插入图片描述
【例 1】默认打开 “a.txt” 文件。

#当前程序文件同目录下没有 a.txt 文件
file = open("a.txt")
print(file)

当以默认模式打开文件时,默认使用 r 权限,由于该权限要求打开的文件必须存在,因此运行此代码会报如下错误:

Traceback (most recent call last):
  File "C:\Users\mengma\Desktop\demo.py", line 1, in <module>
    file = open("a.txt")
FileNotFoundError: [Errno 2] No such file or directory: 'a.txt'

现在,在程序文件同目录下,手动创建一个 a.txt 文件,并再次运行该程序,其运行结果为:

<_io.TextIOWrapper name='a.txt' mode='r' encoding='cp936'>

可以看到,当前输出结果中,输出了 file 文件对象的相关信息,包括打开文件的名称、打开模式、打开文件时所使用的编码格式。

使用 open() 打开文件时,默认采用 GBK 编码。但当要打开的文件不是 GBK 编码格式时,可以在使用 open() 函数时,手动指定打开文件的编码格式,例如:

file = open("a.txt",encoding="utf-8")

注意,手动修改 encoding 参数的值,仅限于文件以文本的形式打开,也就是说,以二进制格式打开时,不能对 encoding 参数的值做任何修改,否则程序会抛出 ValueError 异常,如下所示:

ValueError: binary mode doesn't take an encoding argument

4.1 open()是否需要缓冲区

通常情况下,建议大家在使用 open() 函数时打开缓冲区,即不需要修改 buffing 参数的值。

  • 如果 buffing 参数的值为 0(或者 False),则表示在打开指定文件时不使用缓冲区;
  • 如果 buffing 参数值为大于 1 的整数,该整数用于指定缓冲区的大小(单位是字节);
  • 如果 buffing参数的值为负数,则代表使用默认的缓冲区大小。

为什么呢?原因很简单,目前为止计算机内存的 I/O 速度仍远远高于计算机外设(例如键盘、鼠标、硬盘等)的 I/O 速度,如果不使用缓冲区,则程序在执行 I/O 操作时,内存和外设就必须进行同步读写操作,也就是说,内存必须等待外设输入(输出)一个字节之后,才能再次输出(输入)一个字节。这意味着,内存中的程序大部分时间都处于等待状态。

而如果使用缓冲区,则程序在执行输出操作时,会先将所有数据都输出到缓冲区中,然后继续执行其它操作,缓冲区中的数据会有外设自行读取处理;同样,当程序执行输入操作时,会先等外设将数据读入缓冲区中,无需同外设做同步读写操作。

4.2 open()文件对象常用的属性

成功打开文件之后,可以调用文件对象本身拥有的属性获取当前文件的部分信息,其常见的属性为:

  • file.name:返回文件的名称;
  • file.mode:返回打开文件时,采用的文件打开模式;
  • file.encoding:返回打开文件时使用的编码格式;
  • file.closed:判断文件是否己经关闭。

举个例子:

# 以默认方式打开文件
f = open('my_file.txt')
# 输出文件是否已经关闭
print(f.closed)
# 输出访问模式
print(f.mode)
#输出编码格式
print(f.encoding)
# 输出文件名
print(f.name)

程序执行结果为:

False
r
cp936
my_file.txt

注意,使用 open() 函数打开的文件对象,必须手动进行关闭(后续章节会详细讲解),Python垃圾回收机制无法自动回收打开文件所占用的资源。

5. 以文本格式和二进制格式打开文件,到底有什么区别?

我们知道,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,也可以不用。

6.Python read()函数:按字节(字符)读取文件

本节继续讲解如何读取已打开文件中的数据。

Python 提供了如下 3 种函数,它们都可以帮我们实现读取文件中数据的操作:

  1. read() 函数:逐个字节或者字符读取文件中的内容;
  2. readline() 函数:逐行读取文件中的内容;
  3. readlines() 函数:一次性读取文件中多行内容。

本节先讲解 read() 函数的用法,readline()readlines() 函数会放到后续章节中作详细介绍。

6.1 Python read()函数

对于借助 open() 函数,并以可读模式(包括 rr+rbrb+)打开的文件,可以调用 read() 函数逐个字节(或者逐个字符)读取文件中的内容。

  • 如果文件是以文本模式(非二进制模式)打开的,则 read() 函数会逐个字符进行读取;
  • 反之,如果文件以二进制模式打开,则 read() 函数会逐个字节进行读取。

read() 函数的基本语法格式如下:

file.read([size])

其中,

  • file 表示已打开的文件对象
  • size 作为一个可选参数,用于指定一次最多可读取的字符(字节)个数,如果省略,则默认一次性读取所有内容。

举个例子,首先创建一个名为 my_file.txt 的文本文件,其内容为:

Python教程
http://c.biancheng.net/python/

然后在和 my_file.txt 同目录下,创建一个 file.py 文件,并编写如下语句:

#以 utf-8 的编码格式打开指定文件
f = open("my_file.txt",encoding = "utf-8")
#输出读取到的数据
print(f.read())
#关闭文件
f.close()

程序执行结果为:

Python教程
http://c.biancheng.net/python/

注意,当操作文件结束后,必须调用 close() 函数手动将打开的文件进行关闭,这样可以避免程序发生不必要的错误。

当然,我们也可以通过使用 size 参数,指定 read() 每次可读取的最大字符(或者字节)数,例如:

#以 utf-8 的编码格式打开指定文件
f = open("my_file.txt",encoding = "utf-8")
#输出读取到的数据
print(f.read(6))
#关闭文件
f.close()

程序执行结果为:

Python

显然,该程序中的 read() 函数只读取了 my_file 文件开头的 6 个字符。

再次强调,size 表示的是一次最多可读取的字符(或字节)数,因此,即便设置的 size大于文件中存储的字符(字节)数,read() 函数也不会报错,它只会读取文件中所有的数据

除此之外,对于以二进制格式打开的文件,read() 函数会逐个字节读取文件中的内容。例如:

#以二进制形式打开指定文件
f = open("my_file.txt",'rb+')
#输出读取到的数据
print(f.read())
#关闭文件
f.close()

程序执行结果为:

b'Python\xe6\x95\x99\xe7\xa8\x8b\r\nhttp://c.biancheng.net/python/'

可以看到,输出的数据为 bytes 字节串。我们可以调用 decode() 方法,将其转换成我们认识的字符串。

另外需要注意的一点是,想使用 read() 函数成功读取文件内容,除了严格遵守 read() 的语法外,其还要求 open() 函数必须以可读默认(包括 rr+rbrb+)打开文件。举个例子,将上面程序中 open()的打开模式改为 w,程序会抛出io.UnsupportedOperation异常,提示文件没有读取权限:

Traceback (most recent call last):
  File "C:\Users\mengma\Desktop\file.py", line 3, in <module>
    print(f.read())
io.UnsupportedOperation: not readable

6.2 read()函数抛出UnicodeDecodeError异常的解决方法

在使用 read() 函数时,如果 Python 解释器提示UnicodeDecodeError异常,其原因在于,目标文件使用的编码格式和 open() 函数打开该文件时使用的编码格式不匹配。

举个例子,如果目标文件的编码格式为 GBK 编码,而我们在使用 open() 函数并以文本模式打开该文件时,手动指定 encoding 参数为 UTF-8。这种情况下,由于编码格式不匹配,当我们使用 read() 函数读取目标文件中的数据时,Python 解释器就会提示UnicodeDecodeError异常。

要解决这个问题,要么将 open() 函数中的 encoding 参数值修改为和目标文件相同的编码格式,要么重新生成目标文件(即将该文件的编码格式改为和 open() 函数中的 encoding 参数相同)。

除此之外,还有一种方法:先使用二进制模式读取文件,然后调用 bytes 的 decode() 方法,使用目标文件的编码格式,将读取到的字节串转换成认识的字符串。

举个例子:

#以二进制形式打开指定文件,该文件编码格式为 utf-8
f = open("my_file.txt",'rb+')
byt = f.read()
print(byt)
print("\n转换后:")
print(byt.decode('utf-8'))
#关闭文件
f.close()

程序执行结果为:

b'Python\xe6\x95\x99\xe7\xa8\x8b\r\nhttp://c.biancheng.net/python/'

转换后:
Python教程
http://c.biancheng.net/python/

7.Python readline()readlines()函数:按行读取文件

前面章节中讲到,如果想读取用 open() 函数打开的文件中的内容,除了可以使用 read() 函数,还可以使用 readline()readlines() 函数。

read() 函数不同,这 2 个函数都以“行”作为读取单位,即每次都读取目标文件中的一行。对于读取以文本格式打开的文件,读取一行很好理解;对于读取以二进制格式打开的文件,它们会以“\n”作为读取一行的标志

7.1 Python readline()函数

readline() 函数用于读取文件中的一行,包含最后的换行符“\n”。此函数的基本语法格式为:

file.readline([size])

其中,

  • file 为打开的文件对象;
  • size 为可选参数,用于指定读取每一行时,一次最多读取的字符(字节)数。

read() 函数一样,此函数成功读取文件数据的前提是,使用 open() 函数指定打开文件的模式必须为可读模式(包括
rrbr+rb+ 4 种)

仍以前面章节中创建的 my_file.txt 文件为例,该文件中有如下 2 行数据:

Python教程
http://c.biancheng.net/python/

下面程序演示了 readline() 函数的具体用法:

f = open("my_file.txt")
读取一行数据
byt = f.readline()
print(byt)

程序执行结果为:

Python教程
 

由于 readline() 函数在读取文件中一行的内容时,会读取最后的换行符“\n”,再加上 print()函数输出内容时默认会换行,所以输出结果中会看到多出了一个空行。

不仅如此,在逐行读取时,还可以限制最多可以读取的字符(字节)数,例如:

#以二进制形式打开指定文件
f = open("my_file.txt",'rb')
byt = f.readline(6)
print(byt)

运行结果为:

b'Python'

和上一个例子的输出结果相比,由于这里没有完整读取一行的数据,因此不会读取到换行符。

7.2 Python readlines()函数

readlines() 函数用于读取文件中的所有行,它和调用不指定 size 参数的 read() 函数类似,只不过该函数返回是一个字符串列表,其中每个元素为文件中的一行内容。

readline() 函数一样,readlines() 函数在读取每一行时,会连同行尾的换行符一块读取。

readlines() 函数的基本语法格式如下:

file.readlines()

其中,file 为打开的文件对象。read()readline() 函数一样,它要求打开文件的模式必须为可读模式(包括 rrbr+rb+ 4 种)。

举个例子:

f = open("my_file.txt",'rb')
byt = f.readlines()
print(byt)

运行结果为:

[b'Python\xbd\xcc\xb3\xcc\r\n', b'http://c.biancheng.net/python/']

8.Python write()writelines():向文件中写入数据

前面章节学习了如何使用 read()readline()readlines() 这 3 个函数读取文件,如果我们想把一些数据保存到文件中,又该如何实现呢?

Python 中的文件对象提供了 write() 函数,可以向文件中写入指定内容。该函数的语法格式如下:

file.write(string)

其中,

  • file 表示已经打开的文件对象;
  • string 表示要写入文件的字符串(或字节串,仅适用写入二进制文件中)。

注意,在使用 write() 向文件中写入数据,需保证使用 open() 函数是以 r+ww+aa+的模式打开文件,否则执行 write() 函数会抛出 io.UnsupportedOperation 错误。

例如,创建一个 a.txt 文件,该文件内容如下:

C语言中文网
http://c.biancheng.net

然后,在和 a.txt 文件同级目录下,创建一个 Python 文件,编写如下代码:

f = open("a.txt", 'w')
f.write("写入一行新数据")
f.close()

前面已经讲过,如果打开文件模式中包含 w写入),那么向文件中写入内容时,会先清空原文件中的内容,然后再写入新的内容。因此运行上面程序,再次打开 a.txt 文件,只会看到新写入的内容:

写入一行新数据

如果打开文件模式中包含 a追加),则不会清空原有内容而是将新写入的内容会添加到原内容后边。例如,还原 a.txt 文件中的内容,并修改上面代码为:

f = open("a.txt", 'a')
f.write("\n写入一行新数据")
f.close()

再次打开 a.txt,可以看到如下内容:

C语言中文网
http://c.biancheng.net
写入一行新数据

因此,采用不同的文件打开模式,会直接影响 write() 函数向文件中写入数据的效果。

另外,在写入文件完成后,一定要调用 close() 函数将打开的文件关闭,否则写入的内容不会保存到文件中。例如,将上面程序中最后一行 f.close() 删掉,再次运行此程序并打开 a.txt,你会发现该文件是空的。这是因为,当我们在写入文件内容时,操作系统不会立刻把数据写入磁盘,而是先缓存起来,只有调用 close() 函数时,操作系统才会保证把没有写入的数据全部写入磁盘文件中。

除此之外,如果向文件写入数据后,不想马上关闭文件,也可以调用文件对象提供的 flush() 函数,它可以实现将缓冲区的数据写入文件中。例如:

f = open("a.txt", 'w')
f.write("写入一行新数据")
f.flush()

打开 a.txt 文件,可以看到写入的新内容:

写入一行新数据

有读者可能会想到,通过设置 open() 函数的 buffering 参数可以关闭缓冲区,这样数据不就可以直接写入文件中了?对于以二进制格式打开的文件,可以不使用缓冲区,写入的数据会直接进入磁盘文件;但对于以文本格式打开的文件,必须使用缓冲区,否则 Python 解释器会 ValueError 错误。例如:

f = open("a.txt", 'w',buffering = 0)
f.write("写入一行新数据")

运行结果为:

Traceback (most recent call last):
  File "C:\Users\mengma\Desktop\demo.py", line 1, in <module>
    f = open("a.txt", 'w',buffering = 0)
ValueError: can't have unbuffered text I/O

8.1 Python writelines()函数

Python 的文件对象中,不仅提供了 write() 函数,还提供了 writelines() 函数,可以实现将字符串列表写入文件中。

注意,写入函数只有 write()writelines() 函数,而没有名为 writeline 的函数。

例如,还是以 a.txt 文件为例,通过使用 writelines() 函数,可以轻松实现将 a.txt 文件中的数据复制到其它文件中,实现代码如下:

f = open('a.txt', 'r')
n = open('b.txt','w+')
n.writelines(f.readlines())
n.close()
f.close()

执行此代码,在 a.txt 文件同级目录下会生成一个 b.txt 文件,且该文件中包含的数据和 a.txt 完全一样。

需要注意的是,使用 writelines() 函数向文件中写入多行数据时,不会自动给各行添加换行符

上面例子中,之所以 b.txt 文件中会逐行显示数据,是因为 readlines() 函数在读取各行数据时,读入了行尾的换行符。

9.Python close()函数:关闭文件

在前面章节中,对于使用 open() 函数打开的文件,我们一直都在用 close() 函数将其手动关闭。本节就来详细介绍一下 close() 函数。

close() 函数是专门用来关闭已打开文件的,其语法格式也很简单,如下所示:

file.close()

其中,file 表示已打开的文件对象

读者可能一直存在这样的疑问,即使用 open() 函数打开的文件,在操作完成之后,一定要调用 close() 函数将其关闭吗?答案是肯定的文件在打开并操作完成之后,就应该及时关闭,否则程序的运行可能出现问题

举个例子,分析如下代码:

import os
f = open("my_file.txt",'w')
#...
os.remove("my_file.txt")

代码中,我们引入了 os 模块,调用了该模块中的 remove() 函数,该函数的功能是删除指定的文件。但是,如果运行此程序,Python 解释器会报如下错误:

Traceback (most recent call last):
  File "C:\Users\mengma\Desktop\demo.py", line 4, in <module>
    os.remove("my_file.txt")
PermissionError: [WinError 32] 另一个程序正在使用此文件,进程无法访问。: 'my_file.txt'

显然,由于我们使用了 open() 函数打开了 my_file.txt 文件,但没有及时关闭,直接导致后续的 remove() 函数运行出现错误。因此,正确的程序应该是这样的:

import os
f = open("my_file.txt",'w')
f.close()
#...
os.remove("my_file.txt")

当确定 my_file.txt 文件可以被删除时,再次运行程序,可以发现该文件已经被成功删除了。

再举个例子,如果我们不调用 close() 函数关闭已打开的文件,确定不影响读取文件的操作,但会导致 write() 或者 writeline() 函数向文件中写数据时,写入失败。例如:

f = open("my_file.txt", 'w')
f.write("http://c.biancheng.net/shell/")

程序执行后,虽然 Python 解释器不报错,但打开 my_file.txt 文件会发现,根本没有写入成功。这是因为,在向以文本格式(而不是二进制格式)打开的文件中写入数据时,Python 出于效率的考虑,会先将数据临时存储到缓冲区中,只有使用 close() 函数关闭文件时,才会将缓冲区中的数据真正写入文件中

因此,在上面程序的最后添加如下代码:

f.close()

再次运行程序,就会看到 "http://c.biancheng.net/shell/" 成功写入到了 a.txt 文件。

当然在某些实际场景中,我们可能需要在将数据成功写入到文件中,但并不想关闭文件。这也是可以实现的,调用 flush() 函数即可,例如:

f = open("my_file.txt", 'w')
f.write("http://c.biancheng.net/shell/")
f.flush()

打开 my_file.txt 文件,会发现已经向文件中成功写入了字符串“http://c.biancheng.net/shell/”

10.Python seek()tell()函数详解

在讲解 seek() 函数和 tell() 函数之前,首先来了解一下什么是文件指针

我们知道,使用 open() 函数打开文件并读取文件中的内容时,总是会从文件的第一个字符(字节)开始读起。那么,有没有办法可以自定指定读取的起始位置呢?答案是肯定,这就需要移动文件指针的位置

文件指针用于标明文件读写的起始位置。假如把文件看成一个水流,文件中每个数据(以 b 模式打开,每个数据就是一个字节;以普通模式打开,每个数据就是一个字符)就相当于一个水滴,而文件指针就标明了文件将要从文件的哪个位置开始读起。下图简单示意了文件指针的概念。

可看到,通过移动文件指针的位置,再借助 read()write() 函数,就可以轻松实现,读取文件中指定位置的数据(或者向文件中的指定位置写入数据)。

注意,当向文件中写入数据时,如果不是文件的尾部,写入位置的原有数据不会自行向后移动,新写入的数据会将文件中处于该位置的数据直接覆盖掉。

实现对文件指针的移动,文件对象提供 tell() 函数和 seek() 函数。tell() 函数用于判断文件指针当前所处位置,而 seek() 函数用于移动文件指针到文件的指定位置。

10.1 tell() 函数

tell() 函数的用法很简单,其基本语法格式如下:

file.tell()

其中,file 表示文件对象

例如,在同一目录下,编写如下程序对 a.txt 文件做读取操作,a.txt 文件中内容为:

http://c.biancheng.net

读取 a.txt 的代码如下:

f = open("a.txt",'r')
print(f.tell())
print(f.read(3))
print(f.tell())

运行结果为:

0
htt
3

可以看到,当使用 open() 函数打开文件时,文件指针的起始位置为 0,表示位于文件的开头处,当使用 read() 函数从文件中读取 3 个字符之后,文件指针同时向后移动了 3 个字符的位置。这就表明,当程序使用文件对象读写数据时,文件指针会自动向后移动:读写了多少个数据,文件指针就自动向后移动多少个位置

10.2 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.UnsupportedOperation 错误。

下面程序示范了文件指针操作:

f = open('a.txt', 'rb')
# 判断文件指针的位置
print(f.tell())
# 读取一个字节,文件指针自动后移1个数据
print(f.read(1))
print(f.tell())
# 将文件指针从文件开头,向后移动到 5 个字符的位置
f.seek(5)
print(f.tell())
print(f.read(1))
# 将文件指针从当前位置,向后移动到 5 个字符的位置
f.seek(5, 1)
print(f.tell())
print(f.read(1))
# 将文件指针从文件结尾,向前移动到距离 10 个字符的位置
f.seek(-1, 2)
print(f.tell())
print(f.read(1))

运行结果为:

0
b'h'
1
5
b'/'
11
b'a'
21
b't'

注意:由于程序中使用 seek() 时,使用了非 0 的偏移量,因此文件打开方式中必须包含 b,否则会报io.UnsupportedOperation 错误,有兴趣的读者可自定尝试。

上面程序示范了使用 seek() 方法来移动文件指针,包括从文件开头、指针当前位置、文件结尾处开始计算。运行上面程序,结合程序输出结果可以体会文件指针移动的效果。

11.Python with as用法详解

后续更新…

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值