学python第十一课-文件和异常

学python第十一课-文件和异常

前言


我只是个学python拖了好久的小萌新,一直要学一直要学,刚来注册说到现在,终于开始了。。

这几个月一直在找适合自己的教程,看过几百本电子书,看过无数新老视频教程,最后还是决定用 骆昊大大 的:Python - 100天从新手到大师(zzzain46大大友情推荐)

练习以外的代码应该没有我写的….

软件方面,没有用PyCharm,在用VS Code

python 版本:3.8.1 操作系统:Windows 10

要牢记的话


感谢:https://github.com/jackfrued/Python-100-Days

  • Make English as your working language. (让英语成为你的工作语言
  • Practice makes perfect. (熟能生巧
  • All experience comes from mistakes. (所有的经验都源于你犯过的错误
  • Don’t be one of the leeches. (不要当伸手党
  • Either outstanding or out. (要么出众要么出局

大纲

Day11 - 文件和异常

  • 读文件 - 读取整个文件 / 逐行读取 / 文件路径
  • 写文件 - 覆盖写入 / 追加写入 / 文本文件 / 二进制文件
  • 异常处理 - 异常机制的重要性 / try-except代码块 / else代码块 / finally代码块 / 内置异常类型 / 异常栈 / raise语句
  • 数据持久化 - CSV文件概述 / csv模块的应用 / JSON数据格式 / json模块的应用

文件和异常

实际开发中常常会遇到对数据进行持久化操作的场景,而实现数据持久化最直接简单的方式就是将数据保存到文件中。说到“文件”这个词,可能需要先科普一下关于文件系统的知识,但是这里我们并不浪费笔墨介绍这个概念,请大家自行通过维基百科进行了解。

在Python中实现文件的读写操作其实非常简单,通过Python内置的open函数,我们可以指定文件名、操作模式、编码信息等来获得操作文件的对象,接下来就可以对文件进行读写操作了。这里所说的操作模式是指要打开什么样的文件(字符文件还是二进制文件)以及做什么样的操作(读、写还是追加),具体的如下表所示。

操作模式 具体含义
'r' 读取 (默认)
'w' 写入(会先截断之前的内容)
'x' 写入,如果文件已经存在会产生异常
'a' 追加,将内容写入到已有文件的末尾
'b' 二进制模式
't' 文本模式(默认)
'+' 更新(既可以读又可以写)

下面这张图来自于菜鸟教程网站,它展示了如果根据应用程序的需要来设置操作模式。

img

读写文本文件

读取文本文件时,需要在使用open函数时指定好带路径的文件名(可以使用相对路径或绝对路径)并将文件模式设置为'r'(如果不指定,默认值也是'r'),然后通过encoding参数指定编码(如果不指定,默认值是None,那么在读取文件时使用的是操作系统默认的编码),如果不能保证保存文件时使用的编码方式与encoding参数指定的编码方式是一致的,那么就可能因无法解码字符而导致读取失败。下面的例子演示了如何读取一个纯文本文件。

1
2
3
4
5
6
7
8
def main():
f = open('致橡树.txt', 'r', encoding='utf-8')
print(f.read())
f.close()


if __name__ == '__main__':
main()

请注意上面的代码,如果open函数指定的文件并不存在或者无法打开,那么将引发异常状况导致程序崩溃。为了让代码有一定的健壮性和容错性,我们可以使用Python的异常机制对可能在运行时发生状况的代码进行适当的处理,如下所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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关键字指定文件对象的上下文环境并在离开上下文环境时自动释放文件资源,代码如下所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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-in循环逐行读取或者用readlines方法将文件按行读取到一个列表容器中,代码如下所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import time


def main():
# 一次性读取整个文件内容
with open('致橡树.txt', 'r', encoding='utf-8') as f:
print(f.read())

# 通过for-in循环逐行读取
with open('致橡树.txt', mode='r') as f:
for line in f:
print(line, end='')
time.sleep(0.5)
print()

# 读取文件按行读取到列表中
with open('致橡树.txt') 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中)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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) + '\n')
elif number < 1000:
fs_list[1].write(str(number) + '\n')
else:
fs_list[2].write(str(number) + '\n')
except IOError as ex:
print(ex)
print('写文件时发生错误!')
finally:
for fs in fs_list:
fs.close()
print('操作完成!')


if __name__ == '__main__':
main()

读写二进制文件

知道了如何读写文本文件要读写二进制文件也就很简单了,下面的代码实现了复制图片文件的功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def main():
try:
with open('guido.jpg', 'rb') as fs1:
data = fs1.read()
print(type(data)) # <class 'bytes'>
with open('吉多.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的简单例子。

1
2
3
4
5
6
7
8
9
10
11
{
"name": "骆昊",
"age": 38,
"qq": 957658,
"friends": ["王大锤", "白元芳"],
"cars": [
{"brand": "BYD", "max_speed": 180},
{"brand": "Audi", "max_speed": 280},
{"brand": "Benz", "max_speed": 320}
]
}

可能大家已经注意到了,上面的JSON跟Python中的字典其实是一样一样的,事实上JSON的数据类型和Python的数据类型是很容易找到对应关系的,如下面两张表所示。

JSON Python
object dict
array list
string str
number (int / real) int / float
true / false True / False
null None
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格式保存到文件中,代码如下所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import json


def main():
mydict = {
'name': '骆昊',
'age': 38,
'qq': 957658,
'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对象

这里出现了两个概念,一个叫序列化,一个叫反序列化。自由的百科全书维基百科上对这两个概念是这样解释的:“序列化(serialization)在计算机科学的数据处理中,是指将数据结构或对象状态转换为可以存储或传输的形式,这样在需要的时候能够恢复到原先的状态,而且通过序列化的数据重新获取字节时,可以利用这些字节来产生原始对象的副本(拷贝)。与这个过程相反的动作,即从一系列字节中提取数据结构的操作,就是反序列化(deserialization)”。

目前绝大多数网络数据服务(或称之为网络API)都是基于HTTP协议提供JSON格式的数据,关于HTTP协议的相关知识,可以看看阮一峰老师的《HTTP协议入门》,如果想了解国内的网络数据服务,可以看看聚合数据阿凡达数据等网站,国外的可以看看{API}Search网站。下面的例子演示了如何使用requests模块(封装得足够好的第三方网络访问模块)访问网络API获取国内新闻,如何通过json模块解析JSON数据并显示新闻标题,这个例子使用了天行数据提供的国内新闻数据接口,其中的APIKey需要自己到该网站申请。

1
2
3
4
5
6
7
8
9
10
11
12
13
import requests
import json


def main():
resp = requests.get('http://api.tianapi.com/guonei/?key=APIKey&num=10')
data_model = json.loads(resp.text)
for news in data_model['newslist']:
print(news['title'])


if __name__ == '__main__':
main()

在Python中要实现序列化和反序列化除了使用json模块之外,还可以使用pickle和shelve模块,但是这两个模块是使用特有的序列化协议来序列化数据,因此序列化后的数据只能被Python识别。关于这两个模块的相关知识可以自己看看网络上的资料。另外,如果要了解更多的关于Python异常机制的知识,可以看看segmentfault上面的文章《总结:Python中的异常处理》,这篇文章不仅介绍了Python中异常机制的使用,还总结了一系列的最佳实践,很值得一读。

总结

  1. 了解了如何读写文本文件
  2. 了解了try, except, finally的用法
  3. 了解了with的用法
  4. 了解了如何读写json文件
  5. 了解了json模块还有requests模块

所有用到的链接

补充

https://www.52pojie.cn/forum.php?mod=redirect&goto=findpost&ptid=1254885&pid=33988373

小熊熊,我请教个问题:
我也想学习文件读写,然后我想着例如下面一段信息:
alittlebear61235203360944062512361457843621alittlebear文件读写学习alittlebear一大段的具体文字alittlebear2020年9月5日10:23:59alittlebear0alittlebear

我可以直接写入txt文件,然后读取的时候以alittlebear分割,取出这一行元素。但是txt文件我不用软件直接打开就能看到内容,看到小熊熊你提到的按二进制读写文件,这个是怎么操作的呢?

能用文字简单描述下我上面的这段内容怎么用二进制写入和读取吗?(因为我想着按二进制读写文件,就不能直接通过记事本看到文本了,只能用软件去读取查询)

我太菜了,看不用明白您这里读写二进制的原理,还望大佬指点指点!
https://www.52pojie.cn/thread-1260884-1-1.html

PS:也有可能我对二进制理解有误吧!

https://www.52pojie.cn/forum.php?mod=redirect&goto=findpost&ptid=1254885&pid=33993403

我也是一知半解,说错了请谅解!

首先通过这个网站:https://www.qqxiuzi.cn/bianma/erjinzhi.php

可以将字符串:

alittlebear61235203360944062512361457843621alittlebear文件读写学习alittlebear一大段的具体文字alittlebear2020年9月5日10:23:59alittlebear0alittlebear

转换成二进制:

01100001 01101100 01101001 01110100 01110100 01101100 01100101 01100010 01100101 01100001 01110010 00110110 00110001 00110010 00110011 00110101 00110010 00110000 00110011 00110011 00110110 00110000 00111001 00110100 00110100 00110000 00110110 00110010 00110101 00110001 00110010 00110011 00110110 00110001 00110100 00110101 00110111 00111000 00110100 00110011 00110110 00110010 00110001 01100001 01101100 01101001 01110100 01110100 01101100 01100101 01100010 01100101 01100001 01110010 111001101001011010000111 111001001011101110110110 111010001010111110111011 111001011000011010011001 111001011010110110100110 111001001011100110100000 01100001 01101100 01101001 01110100 01110100 01101100 01100101 01100010 01100101 01100001 01110010 111001001011100010000000 111001011010010010100111 111001101010111010110101 111001111001101010000100 111001011000010110110111 111001001011110110010011 111001101001011010000111 111001011010110110010111 01100001 01101100 01101001 01110100 01110100 01101100 01100101 01100010 01100101 01100001 01110010 00110010 00110000 00110010 00110000 111001011011100110110100 00111001 111001101001110010001000 00110101 111001101001011110100101 00110001 00110000 00111010 00110010 00110011 00111010 00110101 00111001 01100001 01101100 01101001 01110100 01110100 01101100 01100101 01100010 01100101 01100001 01110010 00110000 01100001 01101100 01101001 01110100 01110100 01101100 01100101 01100010 01100101 01100001 01110010

假如说你现在一个txt文件有这段二进制信息,首先

img

读取这个txt文件内的二进制内容

然后可能通过python某个转换功能把这个二进制汉字转换成正常的

alittlebear61235203360944062512361457843621alittlebear文件读写学习alittlebear一大段的具体文字alittlebear2020年9月5日10:23:59alittlebear0alittlebear

我查了半天竟然没找到怎么转换,可能是我方法不对,小本子记下来。

然后输出,如果要修改的话,可能就是先输入内容,然后转换成二进制,然后写入吧

我没实践过,以后有空肯定试试,记下记下

说的不好请见谅,有点困

补充,找到转换办法了,感谢原作者:https://blog.csdn.net/xyq046463/article/details/58606657

https://www.52pojie.cn/forum.php?mod=redirect&goto=findpost&ptid=1254885&pid=33993570

)我也有点懵,这要设计加密方面的知识了,不知道怎么回复菜大大的我只好扯一些有的没的,尽量显得我牛掰一点

以后有机会搞一个文字加密的小脚本,妈妈再也不用担心我的收藏链接被别人发现了

评论

:D 一言句子获取中...

加载中,最新评论有1分钟缓存...