上篇我们围绕字典对象的键值显示,设计了一小段代码,用以实现一个字典对象的所有键值对。说实话,有的时候,用字典dict比自定义类还要好一些,尽管类可以自定义一些有针对性的方法,但在某些场景中,只需要把信息提取出来,然后显示即可,比如在商品的展示,聊天的记录等等。所以,我的建议是,逻辑简单的应用中使用字典,复杂逻辑的再考虑类。
今天,我们不妨把字典对象的键值存储到文件中,然后再想办法读取出来。还是用上一篇的范例单面dict_example.py,我们利用命令行输出定向,保存到本地文件duizhaobiao.txt。见图1、图2
图1 将程序的输出定向到本地文件
图2 文件的保存内容
下面,修改dict_example.py的代码,使之直接在程序中将结果输出到文件,而不是通过命令行终端的功能。主要用到的内置函数为open,以及返回的文件对象的writeline等。其实,open内置函数是python io模块中的一个函数,但正因为是内置函数,我们无需显式地通过from io import open来将open函数导入程序。
图3 添加save_dict函数及调用代码
对应的执行结果见图4和图5:
图4 执行新的代码
图5 新生成的结果文件
对比可以看出与从命令行输出中保存下来的文件并没有什么不同(其实是不一样的!!!python默认的编码为utf-8,而windows控制台里采用的是本地化的字符集,我的电脑上是gbk,只是显示上没有区别而已!!!)。这次的改写,需要注意的有以下几点:1.保存时的换行符只需要添加一个\n即可,尽管对于文本文件windows平台采用的是\r\n的形式,但不要多余写成\r\n的形式,因为会被python替换为两个换行。2. 文件打开open后,一定要有对应的close调用,这不仅是为了防止数据流错误,更是一种好的习惯。3.范例中用了try...except捕获异常和错误的语句,对于文件操作这种可能打开失败报异常或错误的代码而言,用with语句是python推荐使用的。比如上面的代码完全可以修改为:
with open(fn,'w') as f:
...中间省略的其他操作...
f.close()
既简练又方便,python替你完成了错误处理,而我们甚至忘记调用文件对象的close方法也不要紧(但绝对不推荐)。
现在开始编写一个read_dict(fn)函数,用于读取保存在硬盘上的字典数据。总结一下数据的规律,每行的各个键值之间用两个空格进行了分隔。而键值采用了{1s}:{>4s}这种格式。由于ascii码有两位数的情况,意味着例如a: 97,在键与值之间,同样存在着两个空格。这表示,我们不能简单以两个空格为分隔符,对每行数据进行键值利用字符串split简单分开。这里我们使用了正则表达式来进行分割字符串,见图6:
图6 用正则表达式分割字符串
r'(?<!:)\s{2}'表示匹配两个空白符(但前面的字符不能是冒号:);r'(?<=[0-9])\s{2}'表示匹配紧挨着数字0-9的两个空白符。具体详见正则表达式的说明:https://docs.python.org/zh-cn/3.12/library/re.html.
下面,我们编写代码,再从文件中读取出信息,并显示出来,见图7:
图7 读取保存的字典数据
需要说明的是read_dict,按文本方式读取文件时for line in f这行代码,其实对应的是for line in f.readlines()(参见官方文档对readlines方法的说明)。结果见图8:
图8 读取字典文件的处理结果
以上,让我们可以将内存中的字典数据保存下来,并提供了读取出来的能力。这是通过内置open函数实现的。所以不存在额外导入的模块(除了正则表达式模块re)。不过,实际应用中,极少会这样干,最大的理由是不方便,一方面是功能上的,比如判断文件是否存在,另一方面而且可能需要更多的异常判断,为了稳健和安全,需要编写更多的底层代码,python在os模块和pathlib模块中均有比我们自己写的代码更好的实现。不用重复造轮子,抛砖引玉才是最终目的。
另外,对于字典、列表这类数据,特别是小型数据,如果不采用数据库进行存储的话,个人推荐采用json编码的形式进行存储,不仅因为流行,更是为了分享,且有现成的代码模块可以对此做进一步的处理。