第8章 文件处理和异常处理
8.1 问题:Python如何获取文件数据?
Python处理文件的步骤包括打开、读写、关闭。第一步当然就是先要打开文件。要以读文件的模式打开一个文件对象,使用Python内置的open()函数,传入文件名和其他参数。open() 函数常用形式是接收两个参数:文件名(file)和模式(mode),如:
1 |
open(file, mode='r') |
完整的语法格式为:
1 |
open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None) |
其中:
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()函数打开文件具体代码如下:
1 2 3 4 |
myfile = open(r".\data\hello.txt",'r') contents=myfile.read() print(contents) myfile.close() |
运行结果:
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方法会报错。如下所示:
1 2 3 4 5 6 |
myfile = open(r".\data\hello2.txt",'r') FileNotFoundError Traceback (most recent call last) in () ----> 1 myfile= open(r".\data\hello2.txt",'r') FileNotFoundError: [Errno 2] No such file or directory: '.\\data\\hello2.txt' |
这种报错信息叫做异常,如何捕捉异常、如何处理异常等9.4章节将介绍。
8.2基本文件操作
对文件的常用操作包括读取文件,写入文件。读取文件又可以根据文件的大小选择不同的读取方式,如按字节读取、逐行读取、读取整个文件等方式。
8.2.1 读取文件
打开文件后,读取文件使用read()方法。一个文本文件由多行字符串组成,而一行字符串又由多个字符组成。read(size)方法是以字节为单位读取文件内容。比如read(1)就是从当前文件指针位置开始,读取1个字节的内容。如果read()括号中没有数字或是负数,则读取整个文件内容。
(1)按字节读取
下面代码每次从文件中读取固定的1个字节。每次读完后,文件指针会指向下一个字节的位置,就好比用瓢从水缸中舀水,每次都盛出相同的水量。
1 2 3 4 5 6 7 8 |
myfile = open(r'.\data\hello.txt') token = myfile.read(1) print(token) #p token = myfile.read(1) print(token) #y token = myfile.read(2) print(token) #th myfile.close() |
(2)读取整个文件
不指定read()括号中的参数,会读取整个文件内容。
1 2 3 4 5 6 |
myfile = open(r".\data\hello.txt") token = myfile.read() print(token) myfile.close() # Python,java #PyTorch,TensorFlow,Keras |
8.2.2读取文件使用with语句
无论使用哪种高级语言来读取文件,都是先打开磁盘上的一个物理文件,获得一个文件句柄,通过这个句柄(或称作文件对象)来读取,最后再关闭。如果代码中忘记了关闭文件对象,这个文件对象会一直存在于内存中,除非使用close()方法来释放这个文件对象所占用的空间。Python语言为了避免忘记关闭文件,提供了with关键字来自动关闭文件。即使用with格式,就不需要再写close语句了。
1 2 3 4 |
with open(r'.\data\hello.txt') as myfile: print(myfile.read()) #Python,java #PyTorch,TensorFlow,Keras |
8.2.3 逐行读取文件
使用read()方法要么读取整个文件,要么读取固定字节数,总归不太方便。文本文件都是由多行字符串组成,Python也可以逐行读取文件,使用readline()方法。
1 2 3 4 5 |
# cat stu.csv文本文件包含一行标题和三行数据 #no,name,age,gender 01,李康,15,M 02,张平,14,F 03,刘畅,16,M |
(1)逐行读取文件内容并打印
1 2 3 |
with open(r".\data\stu.csv") as myfiles: for line in myfiles: print(line) |
运行结果如下:
no,name,age,gender
01,李康,15,M
02,张平,14,F
03,刘畅,16,M
(2)从上面的打印结果可以看出,行之间多了一个空行。为何出现这种情况?这是因为在文件中,每行的末尾都有一个不可见的换行符(如\n),print语句会加上这个换行符。如何去掉这些空行?只要在print中使用rstrip()或strip()即可:
1 2 3 |
with open(r".\data\stu.csv") as myfiles: for line in myfiles: print(line.rstrip()) |
运行结果:
no,name,age,gender
01,李康,15,M
02,张平,14,F
03,刘畅,16,M
(3)使用readline()可以每次读取一行
使用readline()也会把文件中每行末尾的回车符读进来,如果需要去掉这些空行,同样可以使用rstrip或strip函数。
1 2 3 4 5 |
with open(r".\data\stu.csv") as myfiles: line1=myfiles.readline() print(line1.rstrip()) line2=myfiles.readline() print(line2.rstrip()) |
8.2.4 读取文件所有内容
使用readline()方法虽然可以一次读一行,比使用read(size)方法一次读一个字节方便了不少,但每次运行readline()方法后,文件指针会自动指向下一行,仍然要再调用一次readline()方法,才能读取下一行内容。还是不方便。
(1)使用readlines()读取文件所有内容
Python还提供了readlines()方法一次把文件所有行都读出来,放入到一个列表中。
1 2 3 4 5 |
with open(r".\data\stu.csv") as myfiles: lists=myfiles.readlines() print(type(lists)) for line in lists: print(line.rstrip()) |
下面定义一个类Stu,该类实现利用readlines()函数返回的列表中,并处理每行的每列数据,并打印出每列属性值。
(2)定义类Stu
1 2 3 4 5 6 7 8 9 |
class Stu: def __init__(self,no,name,age,gender): self.no = no self.name = name self.age = age self.gender = gender def debug(self): print("学号:{},姓名:{}, 年龄:{},性别:{}".format(self.no,self.name,self.age,self.gender)) |
(3)处理文件中每列数据
1 2 3 4 5 6 7 8 |
with open(r".\data\stu.csv") as myfiles: for line in myfiles.readlines(): line = line.strip() #不取第一行列名 if (line[0] != 'n'): lst = line.split(',') stu = Stu(lst[0],lst[1],lst[2],lst[3]) stu.debug() |
#上面代码运行的结果如下:
学号:01,姓名:李康, 年龄:15,性别:M
学号:02,姓名:张平, 年龄:14,性别:F
学号:03,姓名:刘畅, 年龄:16,性别:M
上面介绍的三种读文件的方法,都是从文件头开始读,直到遇到文件结束符(EOF)。这种读取方式称作顺序读取。如果一个文件有几个G大小,我想读出其中的一小部分内容,可以采取随机读取方式,使用seek()或tell()方法,有兴趣的读者可以参考Python文档资料。
8.2.5写入文件
write(str)方法把str字符串写入文件,返回值是str字符串的长度。写文件前要先使用追加或写入模式打开文件。
1 2 |
with open(r'.\data\newfile','a') as myfile: myfile.write("hello,Python") |
上面代码中的文件名newfile,如果不存在将自动创建。写入的方式是"a",即追加的方式,如果存在将往里追加记录没有指定扩展名。。但写入的是字符串,仍然是一个文本文件,可以使用记事本查看。write方法写入的字符串最后不会加上回车键\n。
如果要把多行内容写文件,可以每行都调用write方法,Python也提供了writelines(seq)方法一次性写入多行内容。参数seq是一个列表或元祖。
1 2 3 4 5 |
with open(r'.\data\newfile','w') as myfile: seq1 = ["第一行\n","第二行\n"] seq2 = ("第三行\n","第四行\n") myfile.writelines(seq1) myfile.writelines(seq2) |
以'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中是处理程序出错后的代码。其语句结构如下:
1 2 3 4 5 6 7 8 9 10 |
try: <语句> #运行别的代码 except <异常类型>: <语句> #如果在try部份引发了'异常类型'的异常 [except <异常类型>,<数据>: <语句> #如果引发了'异常类型'的异常,获得附加的数据] [else: <语句> #如果没有异常发生] [finally: <语句> #无论代码执行是否成功,都该执行语句] |
try..except代码执行过程类似于 if...else,但后者仅限于可以预知的错误,而使用except是来捕获隐藏的错误。下面代码演示除数为零的异常。
1 2 3 4 5 6 7 8 |
try: num1 = 10 num2 = 0 print(num1 / num2) except: print("除法运行错误,请检查数值") #代码运行结果 #除法运行错误,请检查数值 |
在进行文件操作时,也会出各种异常情况,同样适用try..except语法格式。以下代码中要打开的文件并不存在,程序捕捉到这种异常后,会进入except模块
1 2 3 4 5 6 7 8 |
try: myfile = open("test.txt") myfile.read() myfile.close() except: print("处理文件出错")>log #代码运行结果 #处理文件出错 |
8.3.3 捕获多种异常
异常的种类有多种,针对不同类型的异常可以做区别处理。Python中定义的异常类型有很多种,常见的几种类型可参考表9-3:
表9-3 常见异常种类
异常类名 | 含义 |
AttributeError | 对象缺少属性 |
IOError | 输入/输出操作失败 |
ImportError | 导入模块/对象失败 |
KeyError | 集合中缺少键值错误 |
NameError | 未声明或初始化变量 |
OSError | 操作系统错误 |
StopIteration | 迭代器没有更多的值 |
ZeroDivisionError | 除数为0或用0取模 |
Exception | 常规异常的基类 |
捕获多种异常的语法格式为:
1 2 3 4 5 6 7 8 9 10 |
try: #正常执行代码行a #正常执行代码行b ... ... except 异常类名1 as 变量名1: #处理异常1的代码块 except 异常类名2 as 变量名2: #处理异常2的代码块 except 异常类名3 as 变量名3: #处理异常3的代码块 |
我们把上一节的两种异常代码合并处理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
def demo(): try: num1 = 10 num2 = 0 print(num1 / num2) except ZeroDivisionError as e: print("除法运行错误", e) try: with open("test.txt") as myfile: myfile.read() except FileNotFoundError as e: print("处理文件出错", e) demo() #除法运行错误 division by zero #处理文件出错 [Errno 2] No such file or directory: 'test.txt' |
多个except并列时,try中的代码最先遇到哪个异常种类,就会进入对应的except代码块,而忽略其他的异常种类。except..as 后面的变量名e是为该异常类创建的实例,可以拿到具体的异常信息。
8.3.4 捕获所有异常
既然有那么多的异常种类,我需要每个都捕获么?那样代码写起来太冗长了。Python的每个常规异常类型被定义成了一个类,这些类都有一个共同的父类,就是Exception类。在不需要区分异常类型的情况下,把所有异常都归入Exception类也是通用的做法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
import sys try: with open('myfile.txt') as files: s = files.readline() except IOError as err: print("I/O error: {0}".format(err)) except ValueError: print("Could not convert data to an integer.") except: print("Unexpected error:", sys.exc_info()[0]) raise #程序运行结果 #I/O error: [Errno 2] No such file or directory: 'myfile.txt' |
另外需要注意,如果多个except并列出现,要把Exception基类放在最下面,否则会出现某个异常种类捕捉不到的情况。以下代码是错误的:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
def demo(): try: with open("test.txt") as myfile: myfile.read() except Exception as e1: print("程序运行异常", e1) except IOError as e2: print("IO异常", e2) demo() #运行结果 #程序运行异常 [Errno 2] No such file or directory: 'test.txt' |
8.3.5 清理操作
异常处理中还有一个关键字是finally。final是最终的意思,finally代码块放在所有except代码的后面,无论是否执行了异常代码,finally中的代码都会被执行。
1 2 3 4 5 6 7 8 9 10 11 |
try: num1 = 10 num2 = 0 print(num1 / num2) except Exception as e: print("程序运行异常", e) finally: print("程序运行结束") #程序运行结果 #程序运行异常 division by zero #程序运行结束 |
finally关键字只能出现一次,里面的代码主要完成清理工作。比如关闭文件、关闭数据库链接、记录运行日志等。如下代码把关闭文件放在finally中。
1 2 3 4 5 6 7 |
myfile = open(r".\data\stu.csv") try: print(myfile.read(1)) except Exception as e: print("程序运行异常", e) finally: myfile.close() |
由于try、except、finally分属三个代码块,myfile变量需要定义在外面,以便在代码块中可以引用。
8.3.6 练习
编写一个脚本,实现以下功能:
(1)把用户名、用户登录密码写人文件,至少3条记录,文件名为login.txt
(2)文件login.txt列之间用逗号分割。
(3)用input函数作为一个登录界面,输入用户名、用户密码
(4)用input输入中的用户名及用户密码与文件login.txt中的用户名及密码进行匹对,如果两项都对,提示登录成功,否则提示具体错误,如用户名不存在或密码错误等。