10. 文件 I/O

I/O (输入/输出)是程序设计中很重要的一个概念,输入允许程序读取外部数据(包括来自磁盘、光盘等存储设备的数据),用户输入数据。输出允许程序将数据输出到磁盘、光盘等存储设备中。

Python 提供了非常丰富的 I/O 支持, os 模块中有大量对文件和目录操作的函数,同时在 Python 中也提供了全局的 open() 函数用于打开文件,在文件打开后,程序既可读取文件的内容,也可向文件输出内容。 Python 提供了多种方式来读写文件内容。

10.1. 目录

os 模块下提供了大量操作文件和目录的函数,常用的与目录相关的函数如下所示:

  • os.getcwd():用于获取当前目录
  • os.chdir(path):用于改变当前目录
  • os.fchdir(fd):通过文件描述符改变当前目录,类似于 chdir() 函数
  • os.chroot(path):用于改变当前进程的根目录
  • os.listdir(path):用于返回参数 path 对应目录下的所有文件和子目录
  • os.mkdir(path[, mode]):用于创建参数 path 对应的目录,其中 mode 用于指定该目录的权限
  • os.makedirs(path[, mode]):作用类似于 mkdir() 函数,但函数的功能更加强大,它可以递归创建目录
  • os.rmdir(path):删除 path 对应的空目录。如果目录非空,则抛出一个 OSError 异常
  • os.removedirs(path):递归删除目录
  • os.rename(src, dst):重命名文件或目录,将 src 重名为 dst
  • os.renames(old, new):对文件或目录进行递归重命名

除此之外,在 os.path 模块下还提供了一些其他的涉及到目录的函数,如:

  • os.path.exists(path):用于判断指定目录是否存在

这些函数的具体使用方法如下代码所示:

import os
# 当前目录:
base_path = "/Users/zhaozhiyong/Desktop/my_code/python_course"
cur_path = base_path + "/chapter10"
# 获取当前目录
print(os.getcwd()) # /Users/zhaozhiyong/Desktop/my_code/python_course/chapter10
# 改变当前目录
os.chdir(base_path + "/chapter09")
print(os.getcwd()) # /Users/zhaozhiyong/Desktop/my_code/python_course/chapter09
# 返回指定目录下所有文件和子目录
# ['poem.txt', 'menu.py', 'a.txt']
print(os.listdir(base_path))
# 创建目录
print(os.path.exists(cur_path + "/new_folder")) # False
os.mkdir(cur_path + "/new_folder")
# 删除空目录
try:
	os.rmdir(cur_path + "/new_folder")
except OSError as e:
	print(e)
# 递归创建
os.makedirs(cur_path + "/new_folder1/new_folder2/new_folder3/")
# 递归删除
os.removedirs(cur_path + "/new_folder1/new_folder2/new_folder3/")
# 创建新目录,并修改名字
os.mkdir(cur_path + "/new_folder_name_src")
os.rename(cur_path + "/new_folder_name_src", cur_path + "/new_folder_name_dst")

10.2. 打开文件

要对文件进行 I/O 操作,首先需要打开文件, Python 中提供了 open() 函数用于打开指定文件。

10.2.1. open() 函数

Python 提供的 open() 函数用于打开指定文件。 open() 函数的语法格式如下所示:

open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)

open() 函数的语法格式中,只有第一个参数是必需的,该参数代表要打开文件的路径。其余的参数都带有默认值。 open() 函数返回的是一个文件对象。如果该文件不能被打开,则引发 OSError 错误。

在打开文件之后,就可以调用文件对象的属性和方法。文件对象支持如下常见的属性:

  • file.encoding:返回文件的编码方式
  • file.closed:返回文件是否已经关闭
  • file.mode:返回被打开文件的访问模式
  • file.name:返回被打开文件的名称

具体的使用方法如下代码所示:

dir = "/Users/zhaozhiyong/Desktop/my_code/python_course/chapter10/"
file_name = dir + "a.txt"

f = open(file_name)
# 文件的编码方式
print(f.encoding) # UTF-8
# 文件的访问模式
print(f.mode) # r
# 文件是否己经关闭
print(f.closed) # False
# 文件对象打开的文件名
# /Users/zhaozhiyong/Desktop/my_code/python_course/chapter10/a.txt
print(f.name)

10.2.2. 文件打开模式 mode

open() 函数支持的文件打开模式如下图所示:

输入图片说明

如果程序使用 r 或 r+ 模式打开文件,则要求被打开的文件本身是存在的。也就是说,使用 r 或 r+ 模式都不能创建文件。但如果使用 w 、 w+ 、 a 、 a+ 模式打开文件,则该文件可以是不存在的, open() 函数会自动创建新文件。

10.2.3. 缓冲 buffering

计算机外设(比如硬盘、网络)的速度远远低于访问内存的速度,而程序执行 l/O 时要么将内存中的数据写入外设,要么将外设中的数据读取到内存,如果不使用缓冲,就必须等外设输入或输出一个字节后,内存中的程序才能输出或输入一个字节,这意味着内存中的程序大部分时间都处于等待状态。

open() 函数中, buffering 是一个可选的整数,用于设置缓冲策略。如果 buffering 参数值为 0 时,那么该函数打开的文件就是不带缓冲的,如果该参数的值是 1,则该函数打开的文件就是带缓冲的,此时程序执行 I/O 将具有更好的性能。如果该参数的值是大于 1 的整数,则该整数用于指定缓冲区的大小,其单位是字节。如果该参数的值为任何负数,则代表使用默认的缓冲区大小。

10.3. 读取文件

Python 既可以使用文件对象的方法来读取文件,也可以使用其他模块的函数来读取文件。

10.3.1. 按字节或字符读取

文件对象提供了 read() 方法来按字节或字符读取文件内容,根据 open() 函数中 mode 参数是否设置为 b ,如果设置为 b 则为按字节读取,否则按字符读取。在调用 read() 函数时还可以传入一个整数作为参数,用于指定最多读取多少个字节或宇符。具体使用方法如下代码所示:

dir = "/Users/zhaozhiyong/Desktop/my_code/python_course/chapter01/"
file_name = dir + "a.txt"

f = open(file_name) # 打开文件读操作
while True:
	content = f.read(1) # 读取一个字符
	if not content:
		break
	print(content, end='')
f.close() # 关闭文件

在读写完文件之后,需要显式调用 close() 函数关闭已打开的文件。如果在调用 read() 函数时不传入参数,该方法默认会读取全部文件内容。使用方法如下代码所示:

dir = "/Users/zhaozhiyong/Desktop/my_code/python_course/chapter10/"
file_name = dir + "a.txt"

f = open(file_name)
print(f.read()) # 读取全部内容
f.close()

10.3.2. 按行读取

针对文本文件,文件对象提供了以下两个方法来读取行:

  • readline([n]):读取一行内容。如果指定了参数 n ,则只读取此行内的 n 个字符
  • readlines():读取文件内所有行

readlines() 函数的具体使用方法如下代码所示:

dir = "/Users/zhaozhiyong/Desktop/my_code/python_course/chapter10/"
file_name = dir + "a.txt"

f = open(file_name)
while True:
	# 一次读一行
	line = f.readline()
	# 如果读不到数据,即退出
	if not line:
		break
	print(line.strip())
f.close()

上述代码使用 readline() 函数每次读取一行,当读取为空时,即退出读取。同样,也可以使用 readlines() 函数一次性将文件中的所有行都读出来,具体使用方法如下代码所示:

dir = "/Users/zhaozhiyong/Desktop/my_code/python_course/chapter10/"
file_name = dir + "a.txt"

f = open(file_name)
# 一次性读取所有行
for line in f.readlines():
	print(line.strip())
f.close()

10.3.3 文件迭代器

实际上,文件对象本身就是可遍历的,可以使用 for 循环来遍历文件内容。具体使用方法如下代码所示:

dir = "/Users/zhaozhiyong/Desktop/my_code/python_course/chapter10/"
file_name = dir + "a.txt"

f = open(file_name)
# 使用 for 循环遍历文件对象
for line in f:
	print(line.strip())
f.close()

10.3.4. with 语句

在显式调用 open() 函数打开文件时,都需要显式调用 close() 函数关闭已经打开的文件, open() 函数和 close() 函数都是成对出现的。实际上,在 Python 中提供了 with 语句来管理资源关闭。比如可以把打开的文件放在 with 语句中,这样 with 语句就会帮我们自动关闭文件。 with 语句的语法格式如下所示:

with context_expression [as target(s)]:
	with 代码块

with 语句与文件打开关闭结合的具体使用方法如下代码所示:

dir = "/Users/zhaozhiyong/Desktop/my_code/python_course/chapter10/"
file_name = dir + "a.txt"

# 使用 with 语句管理资源
with open(file_name) as f:
	for line in f:
		print(line.strip())

10.4. 写文件

如果 mode 参数的取值为 r+ww+aa+ ,则采用写入的模式打开文件。几种模式不同的是,当以 r+ww+ 模式打开文件时,文件指针位于文件开头处,而当以 aa+ 模式打开文件时,文件指针位于文件结尾处。此外,当以 ww+ 模式打开文件时,文件会先被清空。

10.4.1. 文件指针

文件指针用于标明文件读写的位置,文件对象提供了以下方法来操作文件指针:

  • seek(offset[, whence]):用于把文件指针移动到指定位置。当 whence 为 0 时,表明从文件开头开始计算,如将 offset 设为 3 ,就是将文件指针移动到第 3 处; 当 whence 为 1 时,表明从指针当前位置开始计算,比如文件指针当前在第 5 处,将 offset 设为 3 ,就是将文件指针移动到第 8 处;当 whence 为 2 时,表明从文件结尾开始计算,比如将 offset 设为 3 ,表明将文件指针移动到文件结尾倒数第 3 处。
  • tell():用于判断文件指针的位置。

具体使用方法如下代码所示:

dir = "/Users/zhaozhiyong/Desktop/my_code/python_course/chapter10/"
file_name = dir + "a.txt"

f = open(file_name, "rb")
# 返回当前的文件指针的位置
print(f.tell()) # 0
# 读 10 个字符
f.read(10)
print(f.tell()) # 10

# 移动文件指针
f.seek(20) # 从头移动 20
print(f.tell()) # 20
f.seek(10, 1) # 从当前位置再移动 10
print(f.tell()) # 30
f.seek(10, 2) # 从末尾倒数 10
print(f.tell()) # 150
f.close()

seek() 函数中设置 whence 参数为 1 或者 2 时, open() 函数的 mode 必须是带有 b 或者 b+,否则只支持从头移动,此时会报错 io.UnsupportedOperation: can't do nonzero cur-relative seeks

10.4.2. 输出内容

文件对象提供的写文件的方法主要有如下两个方法:

  • write() :输出字符串(str)或字节串(bytes)。只有以二进制模式( b 模式)打开的文件才能写入字节串
  • writelines() :输出多个字符串或多个字节串

两个函数的具体使用方法如下代码所示:

import os

dir = "/Users/zhaozhiyong/Desktop/my_code/python_course/chapter10/"
file_name = dir + "poem.txt"

f = open(file_name, "w+")
f.write("静夜思——李白" + os.linesep)
f.writelines(("床前明月光,疑似地上霜。" + os.linesep,
			"举头望明月,低头思故乡。" + os.linesep))
f.close()

在上述代码中, os.linesep 代表当前操作系统上的换行符。 write() 函数只输出了一个字符串,而 writelines() 函数输出了两个字符串。

10.5. 本章小结

本章主要介绍了与 Python I/O 相关的知识,包括如何管理目录和文件。 Python 的 os 模块下提供了大量与 I/O 相关的函数与方法来操作文件和目录, Python 文件读写都需要通过全局 open() 函数,在使用 open() 函数打开文件时可指定不同的模式,通过不同的模式能以二进制文件和文本文件的方式来打开文件,既可打开文件进行读写,也可打开文件进行追加。在打开文件后,程序能以多种方式读取文件内容,即可按字节/字符读取、按行读取、使用文件迭代器读取。在写入文件内容时比较简单,要么使用 write() 方法输出单独的字符串或字节串,要么使用 writelines() 方法批量输出多个字节串和字符串。

本章需要掌握知识点:

  1. 掌握 os 模块下与目录和文件相关的常用函数
  2. 掌握打开文件的 open() 方法
  3. 掌握几种不同的读取文件的方法
  4. 掌握写文件的两种方法