第5章
文件操作
  
  
  
文
件读写是Python编程中的基本操作,它允许程序与外部世界进行数据交换和通信。理解如何进行文件读写操作对于许多应用程序和编程任务都至关重要。
5.1  文件的打开与关闭
  在Python的使用中,经常需要对文件进行读写与存储操作。Python进行文件操作通常涉及打开、读取、写入和关闭文件。
5.1.1  文件类型
  用Python可以处理多种类型的文件,它提供了广泛的库和模块来处理各种文件格式。用户可以根据需要选择适当的库和模块,以便有效地操作和处理不同类型的文件。从数据存储方式来分,文件类型有文本文件和二进制文件。
  1. 文本文件
  文本文件包含文本数据,以纯文本形式存储,通常使用ASCII或UTF-8编码。由于文本文件仅包含纯文本数据,因此它们在不同操作系统之间具有很好的跨平台兼容性。这些文件通常用来存储文本文档、配置文件、日志文件等。常见的文本文件类型有以下几种。
  CSV文件:CSV(Comma-Separated Values)文件是一种常见的文本文件格式,用于存储表格数据。Python中有各种库(如csv模块)可用于处理CSV文件。
  JSON文件:JSON(JavaScript Object Notation)文件是一种用于存储结构化数据的文本文件格式。Python 中有内置的json模块用于解析和生成JSON数据。
  XML文件:XML(eXtensible Markup Language)文件是一种用于存储和交换数据的标记语言。Python库(如 xml.etree.ElementTree)可用于解析和生成 XML 数据。
  2. 二进制文件
  二进制文件包含非文本数据,如图像、音频、视频、压缩文件等。这些文件通常以二进制形式存储,可以使用二进制读写模式来处理。常见的二进制文件类型有以下几种。
  Excel文件:Excel文件是Microsoft Excel电子表格文件,Python中有库(如pandas和openpyxl)可用于处理Excel文件。
  图片文件:图片文件包括各种图像格式,如JPEG、PNG、GIF。Python中有库(如Pillow)可用于处理图像文件。
  音频文件:音频文件包括音频格式,如 MP3、WAV。Python 中有库(如pydub和audioread)可用于处理音频文件。
  视频文件:视频文件包括视频格式,如MP4、AVI。Python 中有库(如moviepy)可用于处理视频文件。
  数据库文件:数据库文件用于存储结构化数据,如 SQLite 数据库文件(.db文件)。Python 中有内置的sqlite3模块可用于操作SQLite数据库。
  日志文件:日志文件包含应用程序的日志信息,用于调试和错误跟踪。Python中有logging模块用于生成和处理日志。
5.1.2  文件的打开
  1. 语法格式
  文件打开的语法格式一:
  
  file = open(file_path, mode, encoding=None, buffering)
      <文件操作>
  
  文件打开的语法格式二:
  
  with open(file_path, mode, encoding=None, buffering) as file:
      <文件操作>
  
  2. 说明
  使用open()函数打开文件后,可以使用文件对象file来对文件进行读取、写入、关闭等操作。如果该文件无法被打开,会抛出OSError异常。
  (1)file_path:文件的路径,可以是文件的绝对路径或相对路径。
  (2)mode:打开文件的模式,具体方式如表5-1所示。如不指定打开文件的模式,默认会使用文本模式。
表5-1  打开文件的模式
模式
描 ? 述
t
文本模式(默认)
b
二进制模式
U
通用换行模式(不推荐)
x
写模式,新建一个文件,如果该文件已存在则会报错
r
以只读方式打开文件。文件的指针将会放在文件的开头。这是默认模式
w
打开一个文件只用于写入。如果该文件已存在则打开文件,并从头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件
a
打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写到已有内容之后。如果该文件不存在,创建新文件进行写入
+
打开一个文件进行更新(可读可写)
rb
以二进制格式打开一个文件用于只读。文件指针将会放在文件的开头。这是默认模式。一般用于非文本文件,如图片等
r+
打开一个文件用于读写。文件指针将会放在文件的开头
                                                                                   续表    
模式
描 ? 述
rb+
以二进制格式打开一个文件用于读写。文件指针将会放在文件的开头。一般用于非文本文件,如图片等
wb
以二进制格式打开一个文件只用于写入。如果该文件已存在则打开文件,并从头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。一般用于非文本文件,如图片等
w+
打开一个文件用于读写。如果该文件已存在则打开文件,并从头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件
wb+
以二进制格式打开一个文件用于读写。如果该文件已存在则打开文件,并从头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。一般用于非文本文件,如图片等
ab
以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写到已有内容之后。如果该文件不存在,创建新文件进行写入
a+
打开一个文件用于读写。如果该文件已存在,文件指针将会放在文件的结尾。文件打开时会是追加模式。如果该文件不存在,创建新文件用于读写
ab+
以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。如果该文件不存在,创建新文件用于读写
  
  (3)encoding:指定了在处理文件时所使用的编码类型,例如,encoding="utf-8"等。指定正确的 encoding 参数以确保文件中的文本数据被正确解释。
  (4)buffering:缓冲参数(可选),用于指定文件的缓冲策略。通常可以省略或用默      认值。
  (5)如采用方式一打开文件且操作完成后,则要使用file.close() 来关闭文件,以释放资源和确保文件完整性。
  通常采用方式二(with语句)来自动管理文件的打开和关闭,它确保在退出with代码块时文件会被正确关闭(即便触发异常也可以)。使用with相比等效的try-finally代码块要简短得多。
  3. file对象
  用open()函数打开文件后,就创建了文件对象file,file对象的部分常用方法如表5-2所示。
表5-2?file对象的部分常用方法
方??法
描?  述
file.close()
关闭文件。关闭后文件不能再进行读写操作
file.flush()
刷新文件内部缓冲,直接把内部缓冲区的数据立刻写入文件,而不是被动的等待输出缓冲区写入
file.fileno()
返回一个整型的文件描述符,可以用在如os模块的read方法等一些底层操作上
file.isatty()
如果文件连接到一个终端设备返回 True,否则返回 False
file.next()
返回文件下一行
file.tell()
返回文件当前位置
file.truncate([size])
截取文件,截取的字节通过size指定,默认为当前文件位置
5.1.3  文件的关闭
  文件关闭的语法格式如下:
  f.close()
  说明:
  通过with语句执行完后,或调用f.close() 关闭文件对象后,再次使用该文件对象将会失败。
5.2  文件的读写
  文件打开后就可以对文件进行读取或写入操作了。根据文件类型的不同,读取文件的方式与写入也不同。
5.2.1  文件的读取
  如以文本文件方式打开文件,则文件读入的是字符串;如以二进制文件方式打开,则读入的是文件字符流。file对象常用的文件读取方法如表5-3所示。
表5-3?file对象常用的文件读取方法
方??法
描?  述
file.read([size int])
从文件读取指定的字节数,返回字符串。
如果size int未给定或为负,则读取所有;如果为正,则读入该行前多少个字节
file.readline([size int])
读取一整行,包括行末的换行符("\n")。
若给定size int>0,则读入该行前多少个字节
file.readlines([size int])
一次性读取文件所有行,包括行末的换行符("\n")。
每行为一个元素构成一个列表。如果sizeint>0,则读入该行前多少个字节
file.seek(offset[, whence])
设置文件当前位置。offset=0为文件头,offset=2为文件尾。
  
  【例5-1】 打开files目录下的Aspirin.txt文件,并显示文件内容。文件内容如图5-1      所示。

图5-1?Aspirin.txt文件
  【任务实现】
  任务分析:用read() 方法从文件读取所有内容,方法返回的结果是字符串,可以直接打印输出。
  具体程序代码和运行结果如图5-2所示。
  
  # 以只读方式打开文件,文件编码方式为 utf-8
  f=open("files/Aspirin.txt",'r',encoding="utf-8")
  # 一次性读入所有内容,返回字符串
  s = f.read()
  print(s)
  # 关闭文件
  f.close()
(a)程序代码

(b)运行结果
图5-2?ccmu.txt文件读取并显示
  【例5-2】 打开files目录下的bj.csv文件,将各个城区名称存到列表中。文件内容如图5-3所示。

图5-3?ccmu1.csv文件
  【任务实现】
  任务分析:CSV文件是一种常见的文本文件格式,用于存储表格数据,其中数据以逗号(或其他分隔符)分隔列,如图5-3所示。一般双击打开CSV文件时,系统会自动用Excel软件打开,但可以用记事本打开看原始文件内容。
  因为要将文件内容中的各个城区名称存到列表中,所以可以用read() 方法从文件读取所有内容。读取的字符串以“,”为分隔符分割出各个元素组成一个列表。
  具体程序代码如图5-4所示。
  
  # 打开文件
  with open("files/bj.csv",'r',encoding="utf-8") as f:
      s = f.read() # 读取文件内容为字符串
  
  # 以“,”为分隔符分割出元素组成一个列表
  ls = s.split(',')
  # 关闭文件
  f.close()
图5-4?bj.csv文件读取并存储为列表
  【例5-3】 打开files文件夹下的schools.txt文件,将各个学院名称存到列表中。文件部分内容如图5-5所示。
  【任务实现】
  任务分析:因为要将文件内容中的各个学院名称存到          列表中,?所以可以用readline()方法从文件逐行读取内容。此外,由于readline()方法返回结果包括行末的换行符('\n'),因此需要使用?strip()方法来去除末尾的换行符再存入列      表中。
  具体程序代码如图5-6所示。
  
  # 打开文件
  with open('files/schools.txt', 'r', encoding="utf-8") as file:
      lines = []                           	# 创建一个空列表来存储每行内容
      while True:
          line = file.readline()         	# 读取一行
          if not line:
              break                        	# 如果读取到文件末尾,跳出循环
          lines.append(line.strip())   	# 用strip()方法去除换行符并将行添加到列表中
  
图5-6?用readline()方法读取ccmu_school.txt文件并存储为列表
  【例5-4】 用readlines() 方法来实现例5-3。
  任务分析:readlines() 方法一次读取所有内容并存到列表中,但每一行行末的换行符('\n')都保存下来了。所以,可以在读取之后再各个列表元素用strip()方法去除末尾的换行符再存入新的列表中。具体程序代码如图5-7所示。
  
  # 打开文件
  with open('files/schools.txt', 'r', encoding="utf-8") as file:
      lns = file.readlines()
  
  # 创建一个空列表来存储每行内容
  lines = []
  # 逐行/元素 用strip()方法去除换行符并添加到新列表中
  for line in lns:
      lines.append(line.strip())
  
图5-7?用readlines()方法读取school.txt文件并存储为列表
5.2.2  文件的写入
  file对象常用的文件写入如表5-4所示。
表5-4?file对象常用的文件写入方法
方??法
描  ?述
file.write(str)
将字符串写入文件,返回的是写入的字符长度
file.writelines(sequence)
向文件写入一个序列字符串列表,如果需要换行则要自己加入每行的换行符
  
  【例5-5】 在bj.csv文件末尾中,添加一个“延庆区”。
  【任务实现】
  任务分析:因为要添加数据,所以使用“a”模式来打开文件,然后使用write() 方法将新数据添加到文件的末尾。具体程序代码如图5-8所示。
  
  with open('files/bj.csv', 'a', encoding="utf-8") as file:
      str = ',延庆区'
      file.write(str)
  
图5-8  文件添加数据
  【例5-6】 2023年8月19日中国医师节主题:“勇担健康使命,铸就时代新功”。将这个主题写入一个新文件,保存名为eg5-6.txt,文件内容如图5-9所示。

图5-9?新文件内容
  【任务实现】
  任务分析:如果该文件不存在,则创建新文件;如果该文件已存在,则覆盖以前生成的文件。所以使用“w”模式来打开文件。另外,由于分两行写入,则需要自己加入每行的换行符“\n”。具体程序代码如图5-10所示。
  【说明】
  由于没有打开文件时,没有指定编码方法,新建文件的编码方式为ANSI。ANSI编码通常采用的编码是“encoding='cp1252'”,是特定于 Windows 操作系统的,而在跨平台和国际化环境中,更推荐使用Unicode编码,如UTF-8或UTF-16,以处理不同语言和字符集的文本。
  
  ls = ['2023年8月19日中国医师节主题:\n', '勇担健康使命,铸就时代新功']
  with open('files/output/eg5-6.txt', 'w') as file:
      file.writelines(ls)
  
图5-10?新建文件写入数据
  【例5-7】 将例3-14中的5位测试者的基本数据(见表3-1)写入文件中,保存名为eg5-7.csv,文件内容如图5-11所示。

图5-11  新数据文件内容
  【任务实现】
  任务分析:使用“w”模式来打开文件。由于数据内容为二维列表,所以采用循环方式将数据逐行写入文件中。具体程序代码如图5-12所示。
  
  # 构建数据集列表: 性别,体重,身高,年龄,平日活动强度
  data =[ [202301,0, 55.5, 165, 18, 2],  [202302,1, 80.5, 185, 18, 4],
          [202303,0, 50.2, 161.3, 23, 1],[202304,1, 72.3, 175.4, 19, 3],
          [202305,1, 76.6, 178.5, 20, 5] ]
  # 打开文件
  with open('files/output/eg5-7.csv', 'w') as file:
      for row in data:
          # 将子列表转换为逗号分隔的字符串
          row_str = ','.join([str(num) for num in row])  
          file.write(row_str + '\n')  # 写入每个子列表字符串并添加换行符
  
图5-12?创建新数据文件
5.3  文件夹的操作
  在Python的文件使用中,经常需要对文件夹进行创建、删除、检查存在性、获取文件夹内容等操作。可使用Python标准库中的os模块来实现这些功能。
5.3.1  os模块
  os全称为Operating System,os模块是Python标准库中的一个核心模块。os模块提供了与操作系统交互的各种方法,例如,创建、删除、移动文件或目录,获取当前工作目录,运行命令行程序等。在用到os模块相应的方法之前,要先导入该库,即“import os”。
  1. 目录及文件操作
  os模块中包含了一些目录及文件操作相关的方法,常用的方法如表5-5所示。
表5-5  常用的目录及文件操作方法
方??法
描  ?述
os.getcwd()
返回当前工作目录
os.chdir(path)
改变当前工作目录
os.listdir(path)
返回path文件夹中包含的文件或文件夹名字的列表
os.mkdir(path)
创建单层目录
os.makedirs(path)
递归创建多层目录
os.rmdir(path)
删除空目录
os.removedirs(path)
递归删除多层空目录
os.remove(path)
删除文件
os.rename(src, dst)
重命名文件或目录
os.walk(path, topdown =True)
遍历目录树,返回一个三元组,包括当前文件夹的路径(root)、当前文件夹中的子文件夹列表(dirs)和当前文件夹中的文件列表(files)。
其中,若topdown=True,遍历顺序是自顶向下,先返回根文件夹,然后是子文件夹;若topdown=False,则自底向上,先返回叶子文件夹,然后是根文件夹
  
  2. 文件路径操作
  os.path是 os 模块的一个子模块,用于路径操作,如查询文件属性,检查文件是否存在以及执行各种路径相关的操作。常用的方法如表5-6所示。
表5-6  常用的文件路径方法
方??法
描  ?述
os.path.join(path, *paths)
接路径的各个部分拼接成一个完整的路径
os.path.exists(path)
检查文件或目录是否存在
os.path.isdir(path)
检查指定路径是否是目录
os.path.isfile(path)
检查指定路径是否是文件
os.path.abspath(path)
返回指定路径的绝对路径
os.path.dirname(path)
返回路径中的目录部分
                                                                                   续表     
方??法
描  ?述
os.path.basename(path)
返回路径中的文件名部分
os.path.split(path)
返回目录和文件名的元组
5.3.2  文件夹操作应用
  【例5-8】 显示文件夹下所有的文件夹或文件名。
  【任务实现】具体程序代码如图5-13所示。
  
  import os
  
  # 指定文件夹的路径
  folder_path = 'D:/MyPython/files'
  # 获取文件夹下所有的文件夹或文件名
  file_list = os.listdir(folder_path)
  
  # 遍历文件列表并打印文件名
  for file in file_list:
      print(file)
  
图5-13  显示文件夹下所有的文件夹或文件名
  【例5-9】 批量创建文件夹。给schools.txt文件中的名称,创建相应的文件夹。
  【任务实现】 具体程序代码如图5-14所示。
  
  import os
  
  # 指定根文件夹路径
  root_folder = "D:/MyPython/files/output/eg5-9"
  
  # 打开文件
  with open('files/schools.txt', 'r', encoding="utf-8") as file:
      # 逐行读取文件内容
      for line in file:
          folder_name = line.strip()            # 去除行尾的换行符
           # 构建新文件夹完整路径
          folder_path = os.path.join(root_folder, folder_name) 
          if not os.path.exists(folder_path):  # 如果该文件夹不存在,则创建文件夹
              os.makedirs(folder_path)  
  
  
图5-14?批量创建文件夹
  【例5-10】 批量删除某文件夹中空的子文件夹。
  【任务实现】 只删除空文件夹,所以要先判断是否为空文件夹。具体程序代码如图5-15所示。
  【思考题】
  程序中为什么是“topdown=False”,而不是“topdown=True”?


  
  import os
  
  # 自定义函数
  def delete_empty_folders(folder_path):
      for root, dirs, files in os.walk(folder_path, topdown=False):
          for dir in dirs:
              dir_path = os.path.join(root, dir)
              if not os.listdir(dir_path):
                  os.rmdir(dir_path)
                  print(f"已删除空文件夹: {dir_path}")
  
  # 指定文件夹路径
  folder_path = "D:/MyPython/files/output"
  
  # 调用函数删除空文件夹
  delete_empty_folders(folder_path)
  
  
图5-15  批量删除空的子文件夹
  【例?5-11】 将例?4-7?所计算实现的输出数据按?CSV?格式保存到?output?文件夹下的“eg5-11.csv”文件中,文件内容如图5-16所示。

图5-16?新CSV文件内容
  【任务实现】
  具体程序代码如图5-17所示。
  
  # 自定义函数,函数参数5个:性别、体重、身高、年龄、平日活动强度,
  # 函数返回结果2个:基础代谢率BMR、每日能量消耗TDEE
  def CalBMR(gender, weight, height, age, atype=1):
      BMR = 0
      TDEE = 0
      # 先按男性公式,计算基础代谢率
      BMR = 10 * weight + 6.25 * height - 5 * age + 5
      # 如果是女生,则在计算数据上做对应调整
      if gender == 0:
          BMR = BMR – 166
      # 计算每日能量消耗
      match atype:
          case 1:  # 久坐或基本不运动
              TDEE = 1.2 * BMR
          case 2:  # 轻度活动
              TDEE = 1.375 * BMR
          case 3:  # 中度活动
              TDEE = 1.55 * BMR
          case 4:  # 积极活动
              TDEE = 1.725 * BMR
          case 5:  # 高强度活动
              TDEE = 1.9 * BMR
          case _:  # 其他
              TDEE = 0
图5-17?计算数据并保存