第8章 文件处理和异常处理

8.1 问题:Python如何获取文件数据?

Python处理文件的步骤包括打开、读写、关闭。第一步当然就是先要打开文件。要以读文件的模式打开一个文件对象,使用Python内置的open()函数,传入文件名和其他参数。open() 函数常用形式是接收两个参数:文件名(file)和模式(mode),如:

完整的语法格式为:

其中:
 file: 必需,文件路径(相对或者绝对路径)。如果是linux环境,路径一般表示为'./data/file_name',如果是windows环境,一般表示为'.\data\file_name',因反斜杠"\"在Python中被视为转义字符,为确保正确,应以原字符串的方式指定路径,即在开头的单引号前加上r。
 mode: 可选,文件打开模式
 buffering: 可选,设置缓冲
 encoding: 可选,一般使用utf8
 errors: 可选,报错级别
 newline: 可选,区分换行符,如\n,\r\n等
 closefd: 可选,传入的file参数类型
 opener: 可选,可以通过调用*opener*来自定义opener。
用open()函数打开文件具体代码如下:

运行结果:
Python,java
PyTorch,TensorFlow,Keras
在操作系统中对文件的操作划分了很多权限,比如读权限、写权限、追加方式写和覆盖方式写等等。Python打开文件的常用语法格式是open(file,mode=’r’),第二个参数是字符,其值有规定的内容和含义,如表9-1所示:
表9-1 mode参数取值和含义

参数值 含义
‘r’ 以只读方式打开已存在的文件
‘w’ 以写入方式打开文件,如不存在则自动创建
‘x’ 以可写入方式打开文件
‘a’ 以追加方式打开文件,新写入的内容会附加在文件末尾
‘b’ 以二进制方式打开文件
‘t’ 以文本方式打开文件
‘+’ 以读写方式打开文件
‘U’ 通用换行符模式(不建议使用)

上面的参数值可以配合使用,比如open(file,’ab’)就是以追加方式打开二进制文件。如果open()方法不写mode参数,mode的默认值是’rt’,即只读方式打开文本文件。
如果要打开的文件并不存在,open方法会报错。如下所示:

这种报错信息叫做异常,如何捕捉异常、如何处理异常等9.4章节将介绍。

8.2基本文件操作

对文件的常用操作包括读取文件,写入文件。读取文件又可以根据文件的大小选择不同的读取方式,如按字节读取、逐行读取、读取整个文件等方式。

8.2.1 读取文件

打开文件后,读取文件使用read()方法。一个文本文件由多行字符串组成,而一行字符串又由多个字符组成。read(size)方法是以字节为单位读取文件内容。比如read(1)就是从当前文件指针位置开始,读取1个字节的内容。如果read()括号中没有数字或是负数,则读取整个文件内容。
(1)按字节读取
下面代码每次从文件中读取固定的1个字节。每次读完后,文件指针会指向下一个字节的位置,就好比用瓢从水缸中舀水,每次都盛出相同的水量。

(2)读取整个文件
不指定read()括号中的参数,会读取整个文件内容。

8.2.2读取文件使用with语句

无论使用哪种高级语言来读取文件,都是先打开磁盘上的一个物理文件,获得一个文件句柄,通过这个句柄(或称作文件对象)来读取,最后再关闭。如果代码中忘记了关闭文件对象,这个文件对象会一直存在于内存中,除非使用close()方法来释放这个文件对象所占用的空间。Python语言为了避免忘记关闭文件,提供了with关键字来自动关闭文件。即使用with格式,就不需要再写close语句了。

8.2.3 逐行读取文件

使用read()方法要么读取整个文件,要么读取固定字节数,总归不太方便。文本文件都是由多行字符串组成,Python也可以逐行读取文件,使用readline()方法。

(1)逐行读取文件内容并打印

运行结果如下:
no,name,age,gender

01,李康,15,M

02,张平,14,F

03,刘畅,16,M
(2)从上面的打印结果可以看出,行之间多了一个空行。为何出现这种情况?这是因为在文件中,每行的末尾都有一个不可见的换行符(如\n),print语句会加上这个换行符。如何去掉这些空行?只要在print中使用rstrip()或strip()即可:

运行结果:
no,name,age,gender
01,李康,15,M
02,张平,14,F
03,刘畅,16,M
(3)使用readline()可以每次读取一行
使用readline()也会把文件中每行末尾的回车符读进来,如果需要去掉这些空行,同样可以使用rstrip或strip函数。

8.2.4 读取文件所有内容

使用readline()方法虽然可以一次读一行,比使用read(size)方法一次读一个字节方便了不少,但每次运行readline()方法后,文件指针会自动指向下一行,仍然要再调用一次readline()方法,才能读取下一行内容。还是不方便。
(1)使用readlines()读取文件所有内容
Python还提供了readlines()方法一次把文件所有行都读出来,放入到一个列表中。

下面定义一个类Stu,该类实现利用readlines()函数返回的列表中,并处理每行的每列数据,并打印出每列属性值。
(2)定义类Stu

(3)处理文件中每列数据

#上面代码运行的结果如下:
学号:01,姓名:李康, 年龄:15,性别:M
学号:02,姓名:张平, 年龄:14,性别:F
学号:03,姓名:刘畅, 年龄:16,性别:M
上面介绍的三种读文件的方法,都是从文件头开始读,直到遇到文件结束符(EOF)。这种读取方式称作顺序读取。如果一个文件有几个G大小,我想读出其中的一小部分内容,可以采取随机读取方式,使用seek()或tell()方法,有兴趣的读者可以参考Python文档资料。

8.2.5写入文件

write(str)方法把str字符串写入文件,返回值是str字符串的长度。写文件前要先使用追加或写入模式打开文件。

上面代码中的文件名newfile,如果不存在将自动创建。写入的方式是"a",即追加的方式,如果存在将往里追加记录没有指定扩展名。。但写入的是字符串,仍然是一个文本文件,可以使用记事本查看。write方法写入的字符串最后不会加上回车键\n。
如果要把多行内容写文件,可以每行都调用write方法,Python也提供了writelines(seq)方法一次性写入多行内容。参数seq是一个列表或元祖。

以'w'模式写入文件时,如果文件已存在,会直接覆盖(相当于删掉后新写入一个文件)。
上面写入文件的字符串要加入回车键,否则即使调用多次writelines()方法,Python执行时也不会自动加上回车。
【说明】
要读取非UTF-8编码的文本文件,需要给open()函数传入encoding参数,例如,读取GBK编码的文件:

更多信息可参考:
https://blog.csdn.net/xrinosvip/article/details/82019844

8.3 异常处理

8.3.1 如何使你的程序更可靠?

写出程序能运行不是我们的目的,写出能运行且不出错的程序才是本事。代码的健壮性和稳定性是衡量一个软件好坏的指标之一。大多数高级语言都提供了异常处理机制来确保代码的健壮性。Python的异常处理语法简单且功能实用,是必须要掌握的要点。

8.3.2 捕获异常

异常处理有两个关键字:try和except。这两个关键字把程序分成两个代码块。try中放置程序正常运行代码,except中是处理程序出错后的代码。其语句结构如下:

try..except代码执行过程类似于 if...else,但后者仅限于可以预知的错误,而使用except是来捕获隐藏的错误。下面代码演示除数为零的异常。

在进行文件操作时,也会出各种异常情况,同样适用try..except语法格式。以下代码中要打开的文件并不存在,程序捕捉到这种异常后,会进入except模块

8.3.3 捕获多种异常

异常的种类有多种,针对不同类型的异常可以做区别处理。Python中定义的异常类型有很多种,常见的几种类型可参考表9-3:
表9-3 常见异常种类

异常类名 含义
AttributeError 对象缺少属性
IOError 输入/输出操作失败
ImportError 导入模块/对象失败
KeyError 集合中缺少键值错误
NameError 未声明或初始化变量
OSError 操作系统错误
StopIteration 迭代器没有更多的值
ZeroDivisionError 除数为0或用0取模
Exception 常规异常的基类

捕获多种异常的语法格式为:

我们把上一节的两种异常代码合并处理:

多个except并列时,try中的代码最先遇到哪个异常种类,就会进入对应的except代码块,而忽略其他的异常种类。except..as 后面的变量名e是为该异常类创建的实例,可以拿到具体的异常信息。

8.3.4 捕获所有异常

既然有那么多的异常种类,我需要每个都捕获么?那样代码写起来太冗长了。Python的每个常规异常类型被定义成了一个类,这些类都有一个共同的父类,就是Exception类。在不需要区分异常类型的情况下,把所有异常都归入Exception类也是通用的做法。

另外需要注意,如果多个except并列出现,要把Exception基类放在最下面,否则会出现某个异常种类捕捉不到的情况。以下代码是错误的:

8.3.5 清理操作

异常处理中还有一个关键字是finally。final是最终的意思,finally代码块放在所有except代码的后面,无论是否执行了异常代码,finally中的代码都会被执行。

finally关键字只能出现一次,里面的代码主要完成清理工作。比如关闭文件、关闭数据库链接、记录运行日志等。如下代码把关闭文件放在finally中。

由于try、except、finally分属三个代码块,myfile变量需要定义在外面,以便在代码块中可以引用。

8.3.6 练习

编写一个脚本,实现以下功能:
(1)把用户名、用户登录密码写人文件,至少3条记录,文件名为login.txt
(2)文件login.txt列之间用逗号分割。
(3)用input函数作为一个登录界面,输入用户名、用户密码
(4)用input输入中的用户名及用户密码与文件login.txt中的用户名及密码进行匹对,如果两项都对,提示登录成功,否则提示具体错误,如用户名不存在或密码错误等。