文件的操作与异常处理
前言
实际开发中常常会遇到对数据进行持久化操作的场景,而实现数据持久化最直接简单的方式就是将数据保存到文件中。说到“文件”这个词,可能需要先科普一下关于文件系统的知识,但是这里我们并不浪费笔墨介绍这个概念,请大家自行通过百度百科进行了解。
一. 读取文件与异常处理
在Python中实现文件的读写操作其实非常简单,通过Python内置的open
函数,我们可以指定文件名、操作模式、编码信息等来获得操作文件的对象,接下来就可以对文件进行读写操作了。这里所说的操作模式是指要打开什么样的文件(字符文件还是二进制文件)以及做什么样的操作(读、写还是追加),具体含义如下表所示。
操作模式 | 具体含义 |
---|---|
'r' | 读取 (默认) |
'w' | 写入(会先覆盖之前的内容) |
'x' | 写入,如果文件已经存在会产生异常 |
'a' | 追加,将内容写入到已有文件的末尾 |
'b' | 二进制模式 |
't' | 文本模式(默认) |
'+' | 更新(既可以读又可以写) |
用这张自于菜鸟教程的图片为例,它展示了如果根据应用程序的需要来设置操作模式,应该是什么样子。
二. 文件的读取
我们再读取文件时,需要在使用open
函数时指定好带路径的文件名(可以使用相对路径或者绝对路径)并将文件模式设置为'r'
(如果不指定,默认值也是'r'
,这样给定的位置不会被改变,有时候关于下划线的问题,会使得计算机识别错误,以为是转义符号等),然后通过encoding
参数指定编码(如果不指定,默认值是None,那么在读取文件时使用的是操作系统默认的编码,最好还是指定一下。),如果不能保证保存文件时使用的编码方式与encoding参数指定的编码方式是一致的,那么就可能因无法解码字符而导致读取失败。下面的例子演示了如何读取一个纯文本文件。
def main():
f = open('可爱的晶晶.txt', 'r', encoding='utf-8')
print(f.read())
f.close()
if __name__ == '__main__':
main()
注: 上面的代码,如果
open
函数指定的文件并不存在或者无法打开,那么将引发异常状况导致程序崩溃。为了让代码有一定的健壮性和容错性,我们可以使用Python的异常机制对可能在运行时发生状况的代码进行适当的处理,如下所示。
def main():
f = None
try:
f = open('可爱的晶.txt', 'r', encoding='utf-8')
print(f.read())
except FileNotFoundError:
print('无法打开指定的文件!')
except LookupError:
print('指定了未知的编码!')
except UnicodeDecodeError:
print('读取文件时解码错误!')
finally:
if f:
f.close()
if __name__ == '__main__':
main()
在Python中,我们可以将那些在运行时可能会出现状况的代码放在
try
代码块中,在try
代码块的后面可以跟上一个或多个except
来捕获可能出现的异常状况。例如在上面读取文件的过程中,文件找不到会引发FileNotFoundError
,指定了未知的编码会引发LookupError
,而如果读取文件时无法按指定方式解码会引发UnicodeDecodeError
,我们在try
后面跟上了三个except
分别处理这三种不同的异常状况。最后我们使用finally
代码块来关闭打开的文件,释放掉程序中获取的外部资源,由于finally
块的代码不论程序正常还是异常都会执行到(甚至是调用了sys
模块的exit
函数退出Python环境,finally
块都会被执行,因为exit
函数实质上是引发了SystemExit
异常),因此我们通常把finally
块称为“总是执行代码块”,它最适合用来做释放外部资源的操作。如果不愿意在finally
代码块中关闭文件对象释放资源,也可以使用上下文语法,通过with
关键字指定文件对象的上下文环境并在离开上下文环境时自动释放文件资源,代码如下所示。
def main():
try:
with open('憨憨的晶晶.txt', 'r', encoding='utf-8') as f:
print(f.read())
except FileNotFoundError:
print('无法打开指定的文件!')
except LookupError:
print('指定了未知的编码!')
except UnicodeDecodeError:
print('读取文件时解码错误!')
if __name__ == '__main__':
main()
# 无法打开指定的文件!
除了使用文件对象的
read
方法读取文件之外,还可以使用for sth-in sth
循环逐行读取或者用readlines
方法将文件按行读取到一个列表容器中,代码如下所示。
import time
def main():
# 一次性读取整个文件内容
with open('可爱的晶晶.txt', 'r', encoding='utf-8') as f:
print(f.read())
# 通过for-in循环逐行读取
with open('可爱的晶晶.txt', mode='r', encoding='utf-8') as f:
for line in f:
print(line, end='')
time.sleep(0.5)
print()
# 读取文件按行读取到列表中
with open('可爱的晶晶.txt', encoding='utf-8') as f:
lines = f.readlines()
print(lines)
if __name__ == '__main__':
main()
一定要指定编码,一定要指定编码,一定要指定编码,重要的事情说三遍。不然让计算机自己“猜”的话,经常会猜错的(或许我运气不好吧。。)。
另外,要将文本信息写入文件中也非常简单,在使用open
函数时指定好文件名并将文件模式设置为'w'
即可。注意如果需要对文件内容进行追加式写入,应该将模式设置为'a'
。如果要写入的文件不存在会自动创建文件而不是引发异常。下面展现一个例子:
"""
如何将1-9999之间的素数分别写入三个文件中(1-99之间的素数保存在a.txt中,100-999之间的素数保存在b.txt中,1000-9999之间的素数保存在c.txt中)。
"""
from math import sqrt
def is_prime(n):
"""判断素数的函数"""
assert n > 0
for factor in range(2, int(sqrt(n)) + 1):
if n % factor == 0:
return False
return True if n != 1 else False
def main():
filenames = ('a.txt', 'b.txt', 'c.txt')
fs_list = []
try:
for filename in filenames:
fs_list.append(open(filename, 'w', encoding='utf-8'))
for number in range(1, 10000):
if is_prime(number):
if number < 100:
fs_list[0].write(str(number) + ",")
elif number < 1000:
fs_list[1].write(str(number) + ",")
else:
fs_list[2].write(str(number) + ",")
except IOError as ex:
print(ex)
print('写文件时发生错误!')
finally:
for fs in fs_list:
fs.close()
print('操作完成!')
if __name__ == '__main__':
main()
# 操作完成!
三. 二进制文件的读写
知道了如何读写文本文件之后,要读写二进制文件方法类似,下面的代码就实现了复制图片文件的功能。
def main():
try:
with open('dd.jpg', 'rb') as fs1:
data = fs1.read()
print(type(data)) # <class 'bytes'>
with open('mm.jpg', 'wb') as fs2:
fs2.write(data)
except FileNotFoundError as e:
print('指定的文件无法打开.')
except IOError as e:
print('读写文件时出现错误.')
print('程序执行结束.')
if __name__ == '__main__':
main()
四. JSON文件的读写
通过上面的讲解,我们已经知道如何将文本数据和二进制数据保存到文件中,那么这里还有一个问题,如果希望把一个列表或者一个字典中的数据保存到文件中又该怎么做呢?答案是将数据以JSON格式进行保存。JSON是“JavaScript Object Notation”的缩写,它本来是JavaScript语言中创建对象的一种字面量语法,现在已经被广泛的应用于跨平台跨语言的数据交换,原因很简单,因为JSON也是纯文本,任何系统任何编程语言处理纯文本都是没有问题的。目前JSON基本上已经取代了XML作为异构系统间交换数据的事实标准。关于JSON的知识,更多的可以参考JSON的官方网站,从这个网站也可以了解到每种语言处理JSON数据格式可以使用的工具或三方库,下面是一个JSON的简单例子。
{
"name": "汤姆猫",
"age": 5,
"qq": 123456789,
"friends": ["狄仁杰", "李元芳"],
"cars": [
{"brand": "BYD", "max_speed": 180},
{"brand": "Audi", "max_speed": 280},
{"brand": "Benz", "max_speed": 320}
]
}
可能大家已经注意到了,上面的JSON跟Python中的字典其实是一样一样的,事实上JSON的数据类型和Python的数据类型是很容易找到对应关系的,如下面两张表所示。
Python | JSON |
---|---|
dict | object |
list, tuple | array |
str | string |
int, float, int- & float-derived Enums | number |
True / False | true / false |
None | null |
我们使用Python中的json模块就可以将字典或列表以JSON格式保存到文件中,代码如下所示。
import json
def main():
mydict = {
'name': '汤姆猫',
'age': 5,
'qq': 123456789,
'friends': ['狄仁杰', '李元芳'],
'cars': [
{'brand': 'BYD', 'max_speed': 180},
{'brand': 'Audi', 'max_speed': 280},
{'brand': 'Benz', 'max_speed': 320}
]
}
try:
with open('data.json', 'w', encoding='utf-8') as fs:
json.dump(mydict, fs)
except IOError as e:
print(e)
print('保存数据完成!')
if __name__ == '__main__':
main()
# 保存数据完成!
json模块主要有四个比较重要的函数,分别是:
dump
- 将Python对象按照JSON格式序列化到文件中dumps
- 将Python对象处理成JSON格式的字符串load
- 将文件中的JSON数据反序列化成对象loads
- 将字符串的内容反序列化成Python对象
# 将文件中的JSON数据反序列化成对象
from json import load
with open("data.json", "r") as f:
data = load(f)
# dump 函数
# 用法:用于把Python的数据格式转变为字符串表达的形式,再存储进文件。
# 参数:第一个是存储的数据内容,第二个是要存储的文件对象。
from json import dump
with open("data.json", "w") as f:
dump([1, 2, 3], f)
# 用法:将字符串里的内容加载成Python的某种数据格式,如果此方法无法加载识别字符串里的数据格式,会报错。
# 参数:要转换的字符串。
from json import loads
list_1 = "[1, 2, 3, 4]"
new_list = loads(list_1)
print(new_list)
# 用法:将Python的某种数据格式转化为字符串,如果此方法无法加载识别数据结构,会报错。
# 参数:要转换的数据。
from json import dumps
list_1 = [1, 2, 3, 4]
new_list = dumps(list_1)
print(type(new_list))
而关于上面的四个函数的方法,设计到几种重要的概念:
-
这里出现了两个概念,一个叫序列化,一个叫反序列化。全球最大的中文百科全书百度百科上对这两个概念是这样解释的:“序列化(serialization)在计算机科学的数据处理中,是指将数据结构或对象状态转换为可以存储或传输的形式,这样在需要的时候能够恢复到原先的状态,而且通过序列化的数据重新获取字节时,可以利用这些字节来产生原始对象的副本(拷贝)。与这个过程相反的动作,即从一系列字节中提取数据结构的操作,就是反序列化(deserialization)”。
-
目前绝大多数网络数据服务(或称之为网络API)都是基于HTTP协议提供JSON格式的数据,关于HTTP协议的相关知识,可以看看阮一峰老师的《HTTP协议入门》,如果想了解国内的网络数据服务,可以看看聚合数据和阿凡达数据等网站,国外的可以看看{API}Search网站。下面的例子演示了如何使用requests模块(封装得足够好的第三方网络访问模块)访问网络API获取国内新闻,如何通过json模块解析JSON数据并显示新闻标题,这个例子使用了天行数据提供的国内新闻数据接口,其中的APIKey需要自己到该网站申请。
-
在Python中要实现序列化和反序列化除了使用json模块之外,还可以使用pickle和shelve模块,但是这两个模块是使用特有的序列化协议来序列化数据,因此序列化后的数据只能被Python识别。关于这两个模块的相关知识可以自己看看网络上的资料。另外,如果要了解更多的关于Python异常机制的知识,可以看看segmentfault上面的文章《总结:Python中的异常处理》,这篇文章不仅介绍了Python中异常机制的使用,还总结了一系列的最佳实践,很值得一读。
总结
本文主要是学了常见文件的读写操作(本文就展示了txt文件,后面学了pandas在展示excel,csv啥的吧)。还有就是之前一直没接触到的异常处理,毕竟在编程过程中,遇到最多的应该就是bug了,因为大家在使用的过程中肯定会出现各种问题,比如一个GUI,我们用户在使用时,例如输入某些内容时,需要的是int型的正整数,而你输入的是个字符串或者float行的小数,一下子就bug了啊。因为最开始编程的话,一般都是理想状态嘛,但到了真的软件开发的时候,肯定是不能的(虽然我也没有开发过软件,哈哈,吹一吹)。最后就是我们日常编程中常见的数据存贮类型了,JSON的相关操作,也是需要在熟练熟练,这对后面我们学习爬虫的时候会有很大的好处。
溜了遛了,脑壳疼。Loading(11/100)。。。