本页面仅用于内容预览,不包含动画交互效果
讲义加载中,请耐心等待……
Python 编程:从入门到实践Python 编程:从入门到实践(第三版)(第三版)Teacher Name / Email1
与《Python 编程:从入门到实践(第三版)》一书配套使用讲义中的文本及绘图采用署名-非商业性使用-相同方式共享协议CC BY-NC-SA 4.0进行许可引用的网络图片附有超链接,可用于访问来源讨论、意见、答疑、勘误、更新:https://github.com/scruel/pcc_3e_slides作者:@Scruel Tao关于本讲义关于本讲义2
1010 文件和异常文件和异常10.1 读取文件10.2 写入文件10.3 异常10.4 存储数据 10.5 小结3
10.110.1 读取文件读取文件Python 能够读写你电脑上的文件,我们可以将各种数据(例如天气数据、文学作品等)存放到文件中,也可以按需读取和处理它们每当我们需要分析或修改存储在文件中的信息时,首先要做的就是读取文件的内容,接下来就让我们来学习这部分知识4
10.110.1 读取文件读取文件让我们来做一些准备工作:新建一个文件夹(比如叫做 file-demo在文件夹中新建两个文件,文件名分别为:pi_digits.txt :待读取的文本文件file_reader.py:主程序文件(下一页给出)pi_digits.txt3.1415926535 8979323846 2643383279文本文件中的内容是精确到小数点 30 位的圆周率值,为了提高可读性,在小数点后每 10 位处换行,并在每次换行后加入两个空格5

示例文件可以手动创建,或者通过以下两个网站之一下载源代码压缩包,并从中提取得到

1. 图灵社区官网的随书下载:

https://www.ituring.com.cn/book/3038

2. 英文版官网的首页:

https://ehmatthes.github.io/pcc_3e/

10.1.110.1.1 读取文件的全部内容读取文件的全部内容打开我们的主程序文件,写入下面的代码:它将读取文本文件的内容到变量中(内存),再将其内容显示到屏幕上file_reader.pyfrom pathlib import Pathpath = Path('pi_digits.txt')contents = path.read_text()print(contents)6

内存:计算机中一个读写速度很快的存储区域,程序运行时所需要的数据一般都存放在其中(文件则存储在硬盘上)。

例如,变量中的的数据(在这里是文本文件中的全部内容)便是存放在内存中的。

(如果你想要学习具体的概念,请阅读计算机组成原理相关书籍)

10.1.110.1.1 读取文件的全部内容读取文件的全部内容打开我们的主程序文件,写入下面的代码,它将读取文本文件的内容到内存,再将其内容显示到屏幕上:file_reader.pyfrom pathlib import Pathpath = Path('pi_digits.txt')contents = path.read_text()print(contents)在第一行中,我们通过 Python 标准库,导入了 pathlib 模块,它能帮助我们在各种操作系统中处理文件和目录。提供一组特定功能的模块也被称为库(librarypathlib path lib7

简写取前三个字母的命名方式,在 Python 中很常见

比如 string 使用 str 表示,integer 使用 int 表示等等,这类简写一般不容易有歧义,且能简化我们的输入,使代码短小精悍

10.1.110.1.1 读取文件的全部内容读取文件的全部内容打开我们的主程序文件,写入下面的代码,它将读取文本文件的内容到内存,再将其内容显示到屏幕上:file_reader.pyfrom pathlib import Pathpath = Path('pi_digits.txt')contents = path.read_text()print(contents)接下来我们将创建一个 Path 类的实例,并将它赋值给 path 变量这个类在实例化时接受一个称为路径的字符串参数路径(path指的是文件或文件夹在系统中的位置8
10.1.110.1.1 读取文件的全部内容读取文件的全部内容打开我们的主程序文件,写入下面的代码,它将读取文本文件的内容到内存,再将其内容显示到屏幕上:file_reader.pyfrom pathlib import Pathpath = Path('pi_digits.txt')contents = path.read_text()print(contents)有了 Path 对象后,可以使 read_text() 方法来读取指定路径对应文件的全部内容,我们将内容赋值给了 contents 变量9
10.1.110.1.1 读取文件的全部内容读取文件的全部内容打开我们的主程序文件,写入下面的代码,它将读取文本文件的内容到内存,再将其内容显示到屏幕上:运行结果3.1415926535 8979323846 2643383279file_reader.pyfrom pathlib import Pathpath = Path('pi_digits.txt')contents = path.read_text()print(contents)10
10.1.110.1.1 读取文件的全部内容读取文件的全部内容运行结果的末尾相比原文件,多了一个空行,因为 read_text()方法在到达文件末尾时会返回一个空字符串,它被视为一个空行运行结果3.1415926535 8979323846 2643383279file_reader.pyfrom pathlib import Pathpath = Path('pi_digits.txt')contents = path.read_text()print(contents)11
10.1.110.1.1 读取文件的全部内容读取文件的全部内容要删除这个多出来的空行,可对字符串变量 contents 调用rstrip() 方法:运行结果3.1415926535 8979323846 2643383279file_reader.pyfrom pathlib import Pathpath = Path('pi_digits.txt') contents = path.read_text() contents = contents.rstrip()print(contents)12
10.1.110.1.1 读取文件的全部内容读取文件的全部内容还可以在一行里完成所有操作file_reader.pyfrom pathlib import Pathpath = Path('pi_digits.txt') contents = path.read_text().rstrip()print(contents)13
10.1.110.1.1 读取文件的全部内容读取文件的全部内容还可以在一行里完成所有操作:代码首先让 Python 执行 read_text() 方法读入文件内容,再对其返回值调用 rstrip() 方法,最后将结果字符串赋值给变量:运行结果3.1415926535 8979323846 2643383279file_reader.pyfrom pathlib import Pathpath = Path('pi_digits.txt') contents = path.read_text().rstrip()print(contents)这种写法称为方法链式调用(method chaining,在编程时很常用。14
10.1.210.1.2 文件路径文件路径在编程中,指定路径的方式有两种:相对文件路径 Python 到相对于当前运行程序的所在目录的指定位置查找绝对文件路径 Python 不管当前运行程序的所在目录,从根目录开始按指定位置查找15
10.1.210.1.2 文件路径:相对路径文件路径:相对路径通过相对路径从银行走到公园16

银行:Bank

公园:Park

10.1.210.1.2 文件路径:相对路径文件路径:相对路径通过相对路径从银行走到公园,先向下(downdown17
10.1.210.1.2 文件路径:相对路径文件路径:相对路径通过相对路径从银行走到公园,先向下(down),然后转向(/down/18

银行:Bank

公园:Park

10.1.210.1.2 文件路径:相对路径文件路径:相对路径通过相对路径从银行走到公园,先向下(down),然后转向(/),再向右走并转向(right/down/right/19
10.1.210.1.2 文件路径:相对路径文件路径:相对路径通过相对路径从银行走到公园,先向下(down),然后转向(/),再向右走并转向(right/),最后向下(downdown/right/down20
10.1.210.1.2 文件路径:相对路径文件路径:相对路径通过相对路径从银行走到公园,先向下(down),然后转向(/),再向右走并转向(right/),最后向下(down)到达目的地down/right/down21
10.1.210.1.2 文件路径:绝对路径文件路径:绝对路径通过绝对路径从银行走到公园22
10.1.210.1.2 文件路径:绝对路径文件路径:绝对路径通过绝对路径从银行走到公园,首先是回到起点(//23
10.1.210.1.2 文件路径:绝对路径文件路径:绝对路径通过绝对路径从银行走到公园,首先是回到起点(/),然后向右走并转向(right//right/24
10.1.210.1.2 文件路径:绝对路径文件路径:绝对路径通过绝对路径从银行走到公园,首先是回到起点(/),然后向右走并转向(right/),随后的路径和相对路径下的一样/right/down/right/down25
10.1.210.1.2 文件路径:绝对路径文件路径:绝对路径通过绝对路径从银行走到公园,首先是回到起点(/),然后向右走并转向(right/),随后的路径和相对路径下的一样/right/down/right/down路径的起点一般叫根(root)文件夹26

在显示文件路径时,Windows 系统使用反斜杠(\)而不是斜杠(/)。但为了避免问题,在你还没有弄明白一切之前,请在代码中始终使用斜杠,即便在 Windows 系统中也是如此。对于复制而来的路径,需要你手动更改其中的字符。

另外,有时候你还会在路径中看到点号:

一个点号 . 表示当前路径,写和不写没有太大区别,但却能使得路径的表达更加清晰。

两个点号 .. 表示上一级路径,比如 /right 的上一级是起点,即 /right/.. 就相当于 /

10.1.210.1.2 文件路径:避免误会文件路径:避免误会假设地图上有两个一样的银行,如果小红按照她片面的观察,告诉小蓝目的地的相对路径27
10.1.210.1.2 文件路径:避免误会文件路径:避免误会假设地图上有两个一样的银行,如果小红按照她片面的观察,告诉小蓝目的地的相对路径,那么小蓝将无法找到小红down28
10.1.210.1.2 文件路径:避免误会文件路径:避免误会使用绝对路径则能够避免当前位置不确切导致的问题,如果小红告诉小蓝绝对路径29
10.1.210.1.2 文件路径:避免误会文件路径:避免误会使用绝对路径则能够避免当前位置不确切导致的问题,如果小红告诉小蓝绝对路径,小蓝将一直能够找到小红,无论他之前身处何处/right/down/right/down30
10.1.310.1.3 访问文件中的各行访问文件中的各行我们时常需要按行处理文本文件,比如查找特定信息,或者修改特定的行等等,可通过字符串的 splitlines() 方法来做到31
10.1.310.1.3 访问文件中的各行访问文件中的各行我们时常需要按行处理文本文件,比如查找特定信息,或者修改特定的行等等,可通过字符串的 splitlines() 方法来做到file_reader.pyfrom pathlib import Pathpath = Path('pi_digits.txt') contents = path.read_text()lines = contents.splitlines()for line in lines: print(contents)在读取文本文件中的所有内容后,我们可以通过调用 splitlines() 方法,获得把文本按行分割所得的列表,这里把它赋给了变量 lines32
10.1.310.1.3 访问文件中的各行访问文件中的各行我们时常需要按行处理文本文件,比如查找特定信息,或者修改特定的行等等,可通过字符串的 splitlines() 方法来做到file_reader.pyfrom pathlib import Pathpath = Path('pi_digits.txt') contents = path.read_text()lines = contents.splitlines()for line in lines: print(contents)在读取文本文件中的所有内容后,我们可以通过调用 splitlines() 方法,获得把文本按行分割所得的列表,这里把它赋给了变量 lines然后,通过一个循环,遍历这些行并打印出来33
10.1.310.1.3 访问文件中的各行访问文件中的各行由于没有修改这些行,所以输出和之前一样,即文件内容:运行结果3.1415926535 8979323846 2643383279file_reader.pyfrom pathlib import Pathpath = Path('pi_digits.txt') contents = path.read_text()lines = contents.splitlines()for line in lines: print(contents)34
10.1.410.1.4 使用文件的内容使用文件的内容将文件的内容读入后,就能按需使用它们了from pathlib import Pathpath = Path('pi_digits.txt')contents = path.read_text()lines = contents.splitlines()pi_string = ''for line in lines: pi_string += lineprint(pi_string)print(len(pi_string))和之前一样,首先读取文件的内容,并将其中的所有行都存储在一个列表中。然后,创建变量 pi_string用于存储圆周率文本。接下来,使用循环追加各行文本35
10.1.410.1.4 使用文件的内容使用文件的内容最后,我们打印出了这个字符串和它的长度:运行结果3.1415926535 8979323846 264338327936from pathlib import Pathpath = Path('pi_digits.txt')contents = path.read_text()lines = contents.splitlines()pi_string = ''for line in lines: pi_string += lineprint(pi_string)print(len(pi_string))36
10.1.410.1.4 使用文件的内容使用文件的内容最后,我们打印出了这个字符串和它的长度:运行结果3.1415926535 8979323846 264338327936由于字符串包含原来位于每行左端的两个空格,我们没有对它们做任何的处理,所以打印的结果中,也包含了这些额外的空格。37
10.1.410.1.4 使用文件的内容使用文件的内容最后,我们打印出了这个字符串和它的长度:由于字符串包含原来位于每行左端的两个空格,我们没有对它们做任何的处理,所以打印的结果中,也包含了这些额外的空格。要删除这些空格,可对每行调用 lstrip() 方法from pathlib import Pathpath = Path('pi_digits.txt')contents = path.read_text()lines = contents.splitlines()pi_string = ''for line in lines: pi_string += line.lstrip()print(pi_string)print(len(pi_string))38
10.1.410.1.4 使用文件的内容使用文件的内容现在再次打印,可以看到空白被删除了:运行结果3.14159265358979323846264338327932from pathlib import Pathpath = Path('pi_digits.txt')contents = path.read_text()lines = contents.splitlines()pi_string = ''for line in lines: pi_string += line.lstrip()print(pi_string)print(len(pi_string))39

读取文本文件和读取用户输入时类似,Python 会将其中的所有文本都解释为字符串。

如果要将读取的数作为数值使用,就必须使用 int() 函数将其转换为整数,或者使用 float() 函数将其转换为浮点数。

10.1.510.1.5 包含包含100100 万位的大型文件万位的大型文件Python 实际上能处理大得多的文件,而不仅仅只有三行的文本通过修改路径,我们可以读入一个包含精确到小数 1 000 000 位的文件,从而创建一个包含所有这些数字的字符串from pathlib import Pathpath = Path('pi_million_digits.txt')contents = path.read_text()lines = contents.splitlines()pi_string = ''for line in lines: pi_string += line.lstrip()40

注意:要运行这个程序(以及后面的众多示例),需要从本书主页下载相关的资源。

示例文件可以通过以下两个网站之一下载源代码压缩包,并从中提取得到

1. 图灵社区官网的随书下载:

https://www.ituring.com.cn/book/3038

2. 英文版官网的首页:

https://ehmatthes.github.io/pcc_3e/

10.1.510.1.5 包含包含100100 万位的大型文件万位的大型文件Python 实际上能处理大得多的文件,而不仅仅只有三行的文本在这里,我们通过对字符串使用切片,让代码只打印到小数点后 50 位,以免屏幕显示不下过多的数字影响效果,其中 52 括了 "3." 这两个字符。from pathlib import Pathpath = Path('pi_million_digits.txt')...print(f"{pi_string[:52]}...")print(len(pi_string))41
10.1.510.1.5 包含包含100100 万位的大型文件万位的大型文件通过验证打印后的结果,我们知道输出如预期一致:运行结果3.14159265358979323846264338327950288419716939937510...100000242

可以回忆一下字符串的定义:字符串是一串字符,因此我们可以把它当作一个列表,并可以使用列表相关的方法,来操作其中的每一个字符。

10.1.610.1.6 圆周率值中包含你的生日吗圆周率值中包含你的生日吗你或许会好奇自己的生日是否在圆周率值中,我们同样可以利用字符串类似列表的特性,使用 in 关键字来检查:for line in lines: pi_string += line.strip()birthday = input("Enter your birthday(yymmdd): ")if birthday in pi_string: print("Your birthday appears!")else: print("Your birthday does not appear.")自己运行试试吧!43

读取文件的内容后,就能以任意 Python 支持的方式,对文本进行分析了。

10.2.110.2.1 写入一行写入一行除了读入文件,我们也能把内存中的内容写到文件中只需要和之前一样指定(输出的)路径,并且使用 write_text() 即可:write_message.pyfrom pathlib import Pathpath = Path('programming.txt')path.write_text('I love programming.')44
10.2.110.2.1 写入一行写入一行除了读入文件,我们也能把内存中的内容写到文件中只需要和之前一样指定(输出的)路径,并且使用 write_text() 即可:这个方法会会在幕后完成几项工作:如果指定路径的文件不存在,则会创建它接着会写入字符串,并在最后确保文件得以妥善地关闭(以防止数据丢失或受损)。write_message.pyfrom pathlib import Pathpath = Path('programming.txt')path.write_text('I love programming.')programming.txtI love programming.45

Python 只能将字符串写入文本文件。如果要将数值数据存储到文本文件中,必须先使用函数 str() 将其转换为字符串格式。

注:路径中的目录部分如果不存在,是不会自动创建的,并且会给出错误。

10.2.210.2.2 写入多行写入多行要将多行写入文件,只需要像往常一样创建多行字符串,然后调用方法即可:首先定义变量 contents用于存储要写入的字符串,末尾的\n是换行符from pathlib import Pathcontents = "I love programming.\n"contents += "I love creating new games.\n"contents += "I also love working with data.\n"path = Path('programming.txt')path.write_text(contents)46
10.2.210.2.2 写入多行写入多行要将多行写入文件,只需要像往常一样创建多行字符串,然后调用方法即可:首先定义变量 contents用于存储要写入的字符串,末尾的\n是换行符接着用 += 运算符,向变量的尾部追加字符串,这种操作可以执行任意多次from pathlib import Pathcontents = "I love programming.\n"contents += "I love creating new games.\n"contents += "I also love working with data.\n"path = Path('programming.txt')path.write_text(contents)47
10.2.210.2.2 写入多行写入多行要将多行写入文件,只需要像往常一样创建多行字符串,然后调用方法即可:接着用 += 运算符,向变量的尾部追加字符串,这种操作可以执行任意多次然后我们把存储在字符串变量中的内容,写入到指定的文件中from pathlib import Pathcontents = "I love programming.\n"contents += "I love creating new games.\n"contents += "I also love working with data.\n"path = Path('programming.txt')path.write_text(contents)48
10.2.210.2.2 写入多行写入多行运行程序后如果再打开文件,将会发现文件的内容已经被替换为刚才的字符串变量中的内容了:要注意的是,由于文件内容将会被替换,所以调用 write_text() 方法时务必要谨慎,否则可能导致不必要的数据丢失programming.txtI love programming.I love creating new games.I also love working with data.49

后面将介绍如何使用 pathlib 检查指定的文件是否存在。

10.310.3 异常异常Python 使用称为异常(exception的特殊对象来管理程序执行期间发生的错误。我们可以使用使用 try-except 代码块来处理异常,如果你不对异常进行处理,程序将停止并给出 traceback,其中包含有关异常原因的报告。学习处理异常能帮助我们防止程序崩溃,使得程序在面对错误的数据时更稳健——不管这些错误数据是源自无意,还是出于破坏程序的恶意企图。50

traceback:回溯,(堆栈的)跟踪信息,包含执行到的异常代码、代码路径、调用过程和异常描述等信息

由于“回溯”两个字容易混淆,所以还是偏向用使用英文原文,这里记住这个单词和大概的含义即可。

10.310.3 异常异常我们可以把异常,当作漏气的轮胎:51
10.310.3 异常异常我们可以把异常,当作漏气的轮胎:一般来说,我们将在 try 中发现轮胎漏气了52
10.310.3 异常异常我们可以把异常,当作漏气的轮胎:一般来说,我们将在 try 中发现轮胎漏气了然后在 catch 修补轮胎53

当然了,如果轮胎已经无法修补了,那就只好更换了:我们可以根据 try 中给出的异常,对应进行针对性处理,如果无法处理,也可 以终止程序的运行。

10.3.110.3.1 除零错误除零错误异常异常你应该知道在数学中,0 不能作为被除数,Python 中也同样不行:division_calculator.pyprint(5 / 0)运行结果Traceback (most recent call last):ll last) File "division_calculator.py", line 1, in <module> print(5 / 0) ~^~ZeroDivisionError: division by zero类似运行结果我们之前已经见过数次了,这是 Python 给出的错误54

其中 ZeroDivisionError 是个异常对象。Python 在无法按你的要求做时,就会创建这种对象。在这种情况下,Python 将停止运行程序,并指出引发了哪种异常,而我们可根据这些信息对程序进行修改来修正代码。

10.3.210.3.2 使用使用 try-excepttry-except 代码块代码块对于刚才的特定异常,我们可以通过编写 try-except 代码块,使 Python 知道如何根据我们的代码来处理:try: ...except ZeroDivisionError: print("You can't divide by zero!")print("Continue!")55
10.3.210.3.2 使用使用 try-excepttry-except 代码块代码块对于刚才的特定异常,我们可以通过编写 try-except 代码块,使 Python 知道如何根据我们的代码来处理:如果运行 try 中的代码没有给出异常,Python 将会跳过 except 代码块try: print(5/5)except ZeroDivisionError: print("You can't divide by zero!") print("Continue!")运行结果1Continue!56
10.3.210.3.2 使用使用 try-excepttry-except 代码块代码块对于刚才的特定异常,我们可以通过编写 try-except 代码块,使 Python 知道如何根据我们的代码来处理:如果运行 try 中的代码没有给出异常,Python 将会跳过 except 代码块否则会尝试匹配给出异常对应的 except 代码块,并执行其中的代码try: print(5/0)except ZeroDivisionError: print("You can't divide by zero!") print("Continue!")57

except ZeroDivisionError 只能匹配处理 ZeroDivisionError 异常

匹配异常的子类

10.3.210.3.2 使用使用 try-excepttry-except 代码块代码块对于刚才的特定异常,我们可以通过编写 try-except 代码块,使 Python 知道如何根据我们的代码来处理:在执行出现错误时,用户将会看到友好的提示信息,而不是 traceback运行结果You can't divide by zero!Continue!try: print(5/0)except ZeroDivisionError: print("You can't divide by zero!") print("Continue!")58
10.3.310.3.3 异常导致的崩溃异常导致的崩溃如果在错误发生时,程序还有工作没有完成,妥善地处理错误就显得尤其重要,比如下面的代码:print("I'll keep dividing the given two numbers.")print("Enter 'q' to quit.")while True: first_number = input("\nFirst number: ") if first_number == 'q': break second_number = input("Second number: ") if second_number == 'q': break answer = int(first_number) / int(second_number) print(answer)59
10.3.310.3.3 异常导致的崩溃异常导致的崩溃如果在错误发生时,程序还有工作没有完成,妥善地处理错误就显得尤其重要,比如下面的代码:print("I'll keep dividing the given two numbers.")print("Enter 'q' to quit.")while True: first_number = input("\nFirst number: ") if first_number == 'q': break second_number = input("Second number: ") if second_number == 'q': break answer = int(first_number) / int(second_number) print(answer)这段代码很简单:持续接受用户的两个数值输入但如果用户输入了 0 作为被除数,Python 就会给出错误60
10.3.310.3.3 异常导致的崩溃异常导致的崩溃如果在错误发生时,程序还有工作没有完成,妥善地处理错误就显得尤其重要,比如下面的代码:但如果用户输入了 0 作为被除数,Python 就会给出错误print("I'll keep dividing the given two numbers.")print("Enter 'q' to quit.")while True: first_number = input("\nFirst number: ") if first_number == 'q': break second_number = input("Second number: ") if second_number == 'q': break answer = int(first_number) / int(second_number) print(answer)由于代码没有采取任何处理错误的措施,所以一旦发生异常,程序就会崩溃61
10.3.310.3.3 异常导致的崩溃异常导致的崩溃程序的崩溃一般是由异常引起62
10.3.310.3.3 异常导致的崩溃异常导致的崩溃程序的崩溃一般是由异常引起的,由于没有做任何处理,所以会给出一个 traceback,不了解程序的用户看到后一定会是一头雾水运行结果I'll keep dividing the given two numbers.Enter 'q' to quit.First number: 5Second number: 0Traceback (most recent call last): File "division_calculator.py", line 11, in <module> answer = int(first_number) / int(second_number) ~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~ZeroDivisionError: division by zero63

程序崩溃可不好,让用户看到 traceback 也不是个好主意。

不懂技术的用户会感到糊涂,怀有恶意的用户还能通过 traceback 获悉你不想让他们知道的信息。

例如,他们将知道你的程序文件的名称,还将看到部分不能正确运行的代码。

有时候,训练有素的攻击者可根据这些信息判断出可对你的代码发起什么样的攻击。

10.3.410.3.4 异常的异常的 elseelse 代码块代码块为了避免意外崩溃,我们用 try-except 代码块们将可能发生异常的代码行包裹起来:--snip--while True: --snip-- if second_number == 'q': break try: answer = ... except ZeroDivisionError: print("You can't divide by zero!") else: print(answer)64

try-except-else

10.3.410.3.4 elseelse 代码块代码块为了避免意外崩溃,我们用 try-except 代码块们将可能发生异常的代码行包裹起来:--snip--while True: --snip-- if second_number == 'q': break try: answer = ... except ZeroDivisionError: print("You can't divide by zero!") else: print(answer)与以往的代码块不太一样,这里额外包含一个 else 其中的代码只有在 try 块中的语句被成功执行(没有给出异常)后才会执行65
10.3.410.3.4 elseelse 代码块代码块为了避免意外崩溃,我们用 try-except 代码块们将可能发生异常的代码行包裹起来:--snip--while True: --snip-- if second_number == 'q': break try: answer = ... except ZeroDivisionError: print("You can't divide by zero!") else: print(answer)except 代码块告诉 Python 如何处理除除零异常66
10.3.410.3.4 elseelse 代码块代码块为了避免意外崩溃,我们用 try-except 代码块们将可能发生异常的代码行包裹起来:--snip--while True: --snip-- if second_number == 'q': break try: answer = ... except ZeroDivisionError: print("You can't divide by zero!") else: print(answer)except 代码块告诉 Python 如何处理除除零异常如果因此失败,就会打印一条友好的消息,跳过 else 代码块67
10.3.410.3.4 elseelse 代码块代码块为了避免意外崩溃,我们用 try-except 代码块们将可能发生异常的代码行包裹起来:--snip--while True: --snip-- if second_number == 'q': break try: answer = ... except ZeroDivisionError: print("You can't divide by zero!") else: print(answer)except 代码块告诉 Python 如何处理除除零异常如果因此失败,就会打印一条友好的消息,跳过 else 代码块如果没有发生异常, else 代码块中的语句会被执行68

通过预测可能发生错误的代码,可编写稳健的程序。它们即便面临无效数据或缺少资源,也能继续运行,不受无意的用户错误和恶意攻击的影响。

10.3.510.3.5 处理处理 FileNotFoundErrorFileNotFoundError 异常异常找不到文件是操作文件时常常会发生的异常,比如文件名不正确等,都会导致这个异常:在没有创建 alice.txt 文件前,直接尝试运行这段代码,Python 将会给出错误,因为文件不存在alice.pyfrom pathlib import Pathpath = Path('alice.txt')contents = path.read_text(encoding='utf-8')69

【拓展】这里使用 read_text() 的方式与前面稍有不同,括号中多了 encoding='utf-8',这指明了读取时所用的文件编码。

如果系统的默认编码与要读取的文件的编码不一致,参数 encoding 必不可少,通过它来指定文件编码可以避免读取时发生错误。

如果要读取的文件不是在你的电脑中创建的(不同电脑的默认编码可能会有所不同),读取时会更容易发生错误。

注:这个知识点放的略有突兀,保留是考虑配套讲义应尽量与原书一致,实际讲解的时可考虑拓展、移后或删除,为了系统性地学习编码的相关知识,个人推荐可以阅读《流畅的 Python》的第四章部分。

10.3.510.3.5 处理处理 FileNotFoundErrorFileNotFoundError 异常异常我们可以分析一下给出的 traceback 信息:运行结果Traceback (most recent call last): File "alice.py", line 4, in <module> contents = path.read_text(encoding='utf-8') ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/.../pathlib.py", line 1056, in read_text with self.open(mode='r', encoding=encoding, errors=errors) as f: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/.../pathlib.py", line 1042, in open return io.open(self, mode, buffering, encoding, errors, newline) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^FileNotFoundError: [Errno 2] No such file or directory: 'alice.txt'70

这里的 traceback 比前面的那些都长,对于这样复杂的 traceback,通常建议先看 traceback 的最后一行。

10.3.510.3.5 处理处理 FileNotFoundErrorFileNotFoundError 异常异常我们可以分析一下给出的 traceback 信息:运行结果Traceback (most recent call last): File "alice.py", line 4, in <module>...FileNotFoundError: [Errno 2] No such file or directory: 'alice.txt'从最后一行可以得知,代码引发的是 FileNotFoundError 异常。这一点很重要,它让我们知道应该在要编写的 except 代码块中使用哪种异常。71
10.3.510.3.5 处理处理 FileNotFoundErrorFileNotFoundError 异常异常再从头阅读 traceback运行结果Traceback (most recent call last): File "alice.py", line 4, in <module> contents = path.read_text(encoding='utf-8') ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^...FileNotFoundError: [Errno 2] No such file or directory: 'alice.txt'72
10.3.510.3.5 处理处理 FileNotFoundErrorFileNotFoundError 异常异常再从头阅读 traceback运行结果Traceback (most recent call last): File "alice.py", line 4, in <module> contents = path.read_text(encoding='utf-8') ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^...FileNotFoundError: [Errno 2] No such file or directory: 'alice.txt'从第二行可知,错误发生在文件 alice.py 的第四行。接下来的一行列出了导致错误的代码行内容。73
10.3.510.3.5 处理处理 FileNotFoundErrorFileNotFoundError 异常异常我们可以分析一下给出的 traceback 信息:运行结果Traceback (most recent call last): File "alice.py", line 4, in <module> contents = path.read_text(encoding='utf-8') ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/.../pathlib.py", line 1056, in read_text with self.open(mode='r', encoding=encoding, errors=errors) as f: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/.../pathlib.py", line 1042, in open return io.open(self, mode, buffering, encoding, errors, newline) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^FileNotFoundError: [Errno 2] No such file or directory: 'alice.txt'74

往下的其余部分列出了一些代码,它们来自打开和读取文件涉及的库。通常,不需要详细阅读和理解 traceback 中的这些内容。

10.3.510.3.5 处理处理 FileNotFoundErrorFileNotFoundError 异常异常为了处理这个异常,应将 traceback 指出的存在问题的代码行放 try 代码块中,即包含 read_text() 的代码行:from pathlib import Pathpath = Path('alice.txt')contents = path.read_text(encoding='utf-8')75
10.3.510.3.5 处理处理 FileNotFoundErrorFileNotFoundError 异常异常为了处理这个异常,应将 traceback 指出的存在问题的代码行放 try 代码块中,即包含 read_text() 的代码行:from pathlib import Pathpath = Path('alice.txt')try: contents = path.read_text(encoding='utf-8')except FileNotFoundError: print(f"Sorry, the file {path} does not exist.")运行结果Sorry, the file alice.txt does not exist.76

这样,当找不到文件时,Python 将运行 except 代码块中的代码,从而显示一条友好的错误消息,而不是 traceback。

10.3.510.3.5 处理处理 FileNotFoundErrorFileNotFoundError 异常异常from pathlib import Pathpath = Path('alice.txt')try: contents = path.read_text(encoding='utf-8')except FileNotFoundError: print(f"Sorry, the file {path} does not exist.")而对于成功读入的文本,我们可以做一些分析,例如尝试计算它包含多少个单词。77
10.3.510.3.5 处理处理 FileNotFoundErrorFileNotFoundError 异常异常from pathlib import Pathpath = Path('alice.txt')try: contents = path.read_text(encoding='utf-8')except FileNotFoundError: print(f"Sorry, the file {path} does not exist.")else: # 计算文件大致包含多少个单词 words = contents.split() num_words = len(words) print(f"The file {path} has about {num_words} words.")78
10.3.610.3.6 分析文本分析文本...try:...else: # 计算文件大致包含多少个单词 words = contents.split() num_words = len(words) print(f"The file {path} has about {num_words} words.")这些代码都放在 else 代码块中,仅当 try 块成功执行(没有异常)时,才会执行它们。79
10.3.610.3.6 分析文本分析文本...try:...else: # 计算文件大致包含多少个单词 words = contents.split() num_words = len(words) print(f"The file {path} has about {num_words} words.")对变量 contents 调用 split 方法,我们将得到一个列表,其中包含这部童话中的所有单词。80
10.3.610.3.6 分析文本分析文本...try:...else: # 计算文件大致包含多少个单词 words = contents.split() num_words = len(words) print(f"The file {path} has about {num_words} words.")通过对这个列表调用 len,我们就可知道原始字符串大致包含多少个单词。81

最后,打印一条消息,指出文件包含多少个单词。

10.3.610.3.6 分析文本分析文本现在将文件 alice.txt 移动到执行程序的相同目录中,让 try 代码块能够成功地执行,执行结果如下:运行结果The file alice.txt has about 29594 words.原来童话《爱丽丝漫游奇境记》中有这么多单词!82

将文件放到哪里?请回忆路径相关知识,要补充的一点是,相对路径是以程序运行的所在目录为起点的。

注:实际上,输出的数会略微偏大,因为这里使用的文本文件包含出版商提供的额外信息,但它与童话爱丽丝漫游奇境记(Alice in Wonderland)的长度基本一致。

10.3.710.3.7 使用多个文件使用多个文件我们还可以多分析几本书,为此创建一个函数:from pathlib import Pathdef count_words(path): """计算一个文件大致包含多少个单词""" try: contents = path.read_text(encoding='utf-8') except FileNotFoundError:( print(f"Sorry, the file {path} does not exist.")) else: # 计算文件大致包含多少个单词 words = contents.split() num_words = len(words) print(f"The file {path} has about {num_words} words.")83

这些代码大多与原来一样,只是被移到了函数 count_words()中,并且增加了缩进量。

10.3.710.3.7 使用多个文件使用多个文件我们还可以多分析几本书,为此创建一个函数:from pathlib import Pathdef count_words(path): """计算一个文件大致包含多少个单词""" try: contents = path.read_text(encoding='utf-8') except FileNotFoundError:( print(f"Sorry, the file {path} does not exist.")) else: # 计算文件大致包含多少个单词 words = contents.split() num_words = len(words) print(f"The file {path} has about {num_words} words.")84

在修改程序的同时更新注释是个不错的习惯,因此我们将注释改成了文档字符串,并稍微调整了一下措辞

10.3.710.3.7 使用多个文件使用多个文件现在可以编写一个简短的循环,计算要分析的任何文本包含多少个单词了:from pathlib import Pathdef count_words(path): --snip--filenames = ['alice.txt', 'siddhartha.txt', 'moby_dick.txt','little_women.txt']for filename in filenames: path = Path(filename) count_words(path)85
10.3.710.3.7 使用多个文件使用多个文件from pathlib import Pathdef count_words(path): --snip--filenames = ['alice.txt', 'siddhartha.txt', 'moby_dick.txt','little_women.txt']for filename in filenames: path = Path(filename) count_words(path)文件名都是简单字符串,我们在循环中将它们转换为 Path 对象,再调用 count_words 函数。86
10.3.710.3.7 使用多个文件使用多个文件当我们把列出的文本文件放入目录(但故意不将文件 siddhartha.txt 放入目录,这丝毫不影响这个程序处理其他文件)后,运行的结果如下:运行结果The file alice.txt has about 29594 words.Sorry, the file siddhartha.txt does not exist.The file moby_dick.txt has about 215864 words.The file little_women.txt has about 189142 words.87

在这个示例中,使用 try-except 代码块有两个重要的优点:

一是避免用户看到 traceback,二是让程序可以继续分析能够找到的其他文件。如果不捕获因找不到 siddhartha.txt 而引发的 FileNotFoundError 异常,用户将看到让一般人困惑的完整 traceback,并且程序也将在尝试分析 Siddhartha 后停止运行——根本不会继续分析 Moby Dick 和 Little Women。

10.3.710.3.7 使用多个文件使用多个文件运行结果The file alice.txt has about 29594 words.Sorry, the file siddhartha.txt does not exist.The file moby_dick.txt has about 215864 words.The file little_women.txt has about 189142 words.程序如期完成了运行,并友好提示了 siddhartha.txt 件不存在。88
10.3.710.3.7 使用多个文件使用多个文件运行结果The file alice.txt has about 29594 words.Sorry, the file siddhartha.txt does not exist.The file moby_dick.txt has about 215864 words.The file little_women.txt has about 189142 words.如果不捕获因找不到文件而引发的 FileNotFoundError 异常,用户将看到让一般人困惑的完整 traceback89
10.3.710.3.7 使用多个文件使用多个文件运行结果The file alice.txt has about 29594 words.Sorry, the file siddhartha.txt does not exist.The file moby_dick.txt has about 215864 words.The file little_women.txt has about 189142 words.如果不捕获因找不到文件而引发的 FileNotFoundError 异常,用户将看到让一般人困惑的完整 traceback且程序也将在尝试分析 Siddhartha 后停止运行——根本不会继续分析 Moby Dick Little Women90
10.3.710.3.7 使用多个文件使用多个文件运行结果The file alice.txt has about 29594 words.Sorry, the file siddhartha.txt does not exist.The file moby_dick.txt has about 215864 words.The file little_women.txt has about 189142 words.可以看到在这个示例中,使用 try-except 代码块有两重要的优点一是避免用户看到易让人困惑的 traceback二是让程序可以继续分析能够找到的其他文件91
10.3.810.3.8 静默失败静默失败在异常发生后,可以通过 pass 关键字来表示什么都不做,这样既不会给出 traceback,也不会给出友好的错误提示:...try: contents = path.read_text(encoding='utf-8')except FileNotFoundError: passprint("Done.")运行结果Done.92

要注意的是,如果你不知道代码写成这样是否合适,那最好不要这样做,静默失败容易导致发生“看不见”的问题,当程序执行后的预期结果不一致时,会使得你难以排查问题的原因。pass 关键字很有用,我们以后还会遇到它。

10.3.910.3.9 决定报告哪些错误决定报告哪些错误该在什么情况下向用户报告错误?又该在什么情况下静默失败呢?如果用户知道要分析哪些文件,他们可能希望在有文件(因不存在而)未被分析时,能够收到一条提醒消息。如果用户只想看到结果,并不知道要分析哪些文件,可能就无须在有些文件不存在时告知他们。向用户显示他们不想看到的信息,可能会降低可用性和易用性Python 的错误处理结构让你能够细致地控制与用户共享错误信息的程度,要共享多少信息,完全由你决定。93

编写得很好且经过恰当测试的代码不容易出现内部错误,如语法错误和逻辑错误,但只要程序依赖于外部因素,如用户输入、是否存在指定的文件、是否有网络连接,就有可能出现异常。我们凭借经验判断该在程序的什么地方包含异常处理块,以及出现错误时该向用户提供多少相关的信息。

10.410.4 存储数据存储数据很多程序要求用户输入某种信息,比如让用户存储游戏首选项或提供要可视化的数据,运行时一般会将数据保存在某个数据结构中。不过当用户关闭程序时,我们又如何保存他们提供的信息一种简单的方式是使用模块 json,它提供了存储数据的相关函数。94
10.410.4 存储数据存储数据模块 json 让你能很容易地将 Python 数据结构转换 JSON 式的字符串,并在程序再次运行时从文件中加载数据。你还可以使用 JSON Python 程序之间共享数据更重要的是,JSON 数据格式并不是 Python 专用的,这让你能够将以 JSON 格式存储的数据与使用其他编程语言的人共享这是一种轻量级数据格式,不仅很有用,也易于学习。下面先编写一个存储一组数的简短程序,再编写一个将这些数读取到内存中的程序。95

JSON(JavaScript Object Notation)格式最初是为JavaScript 开发的,但随后成了一种通用的格式,被包括Python 在内的众多语言采用。

10.4.110.4.1 使用使用 jsonjson 第一个程序将使用 json.dumps() 来存储这组数number_writer.pyfrom pathlib import Pathimport jsonnumbers = [2, 3, 5, 7, 11, 13]path = Path('numbers.json')contents = json.dumps(numbers)path.write_text(contents)96
10.4.110.4.1 使用使用 jsonjson 第一个程序将使用 json.dumps() 来存储这组数,运行后将生成指定的 numbers.json 文件,文件保存在本地number_writer.pyfrom pathlib import Pathimport jsonnumbers = [2, 3, 5, 7, 11, 13]path = Path('numbers.json')contents = json.dumps(numbers)path.write_text(contents)numbers.json[2, 3, 5, 7, 11, 13]97
10.4.110.4.1 使用使用 jsonjson 第二个程序将读取刚才的本地文件,随后使用 json.loads 函数来读取它们number_reader.pyfrom pathlib import Pathimport jsonpath = Path('numbers.json')contents = path.read_text()numbers = json.loads(contents)print(numbers)运行结果[2, 3, 5, 7, 11, 13]98
10.4.210.4.2 保存和读取用户生成的数据保存和读取用户生成的数据下面来看一个这样的例子:提示用户在首次运行程序时输入自己的名字,并且在他再次运行程序时仍然记得他。remember_me.pyfrom pathlib import Pathimport jsonusername = input("What is your name? ")path = Path('username.json')contents = json.dumps(username)path.write_text(contents)print(f"We'll remember you when you come back, {username}!")99
10.4.210.4.2 保存和读取用户生成的数据保存和读取用户生成的数据首先,提示用户输入名字。接下来,将收集到的数据写入文件username.json。然后,打印一条消息,指出存储了用户输入的信息:运行结果What is your name? EricWe'll remember you when you come back, Eric!100
10.4.210.4.2 保存和读取用户生成的数据保存和读取用户生成的数据现在再编写一个程序,向名字已被存储的用户发出问候:greet_user.pyfrom pathlib import Pathimport jsonpath = Path('username.json')contents = path.read_text()username = json.loads(contents)print(f"Welcome back, {username}!")运行结果Welcome back, Eric!101

我们读取数据文件的内容,并使用 json.loads() 将恢复的数据赋给变量 username。有了已恢复的用户名,就可以使用个性化的问候语欢迎用户回来了:

10.4.210.4.2 保存和读取用户生成的数据保存和读取用户生成的数据可以将这两个程序合并到一个程序中:remember_me.pyfrom pathlib import Pathimport jsonpath = Path('username.json')if path.exists(): contents = path.read_text() username = json.loads(contents) print(f"Welcome back, {username}!")else: username = input("What is your name? ") contents = json.dumps(username) path.write_text(contents) print(f"We'll remember you when you come back, {username}!")102

这里原本可以编写一个 try-except 代码块,以便在文件 username.json 不存在时采取合适的措施,但我们没有这样做,而是使用 了pathlib 模块提供的一个便利方法 path.exists()。如果文件 username.json 不存在,就提示用户输入用户名,并存储用户输入的值。此外,还会打印一条消息,指出当用户再回来时我们还会记得他。

无论执行的是哪个代码块,都将显示用户名和合适的问候语。

10.4.210.4.2 保存和读取用户生成的数据保存和读取用户生成的数据remember_me.py...if path.exists(): contents = path.read_text() username = json.loads(contents) print(f"Welcome back, {username}!")else: username = input("What is your name? ") contents = json.dumps(username) path.write_text(contents) print(f"We'll remember you when you come back, {username}!")这里使用了 pathlib 模块提供的便利方法 exists,避免了文件不存在导致的异常,所以不需要异常捕获块。103

如果文件 username.json 不存在,就提示用户输入用户名,并存储用户输入的值。此外,还会打印一条消息,指出当用户再回来时我们还会记得他。

无论执行的是哪个代码块,都将显示用户名和合适的问候语。

10.4.310.4.3 重构重构你经常会遇到这样的情况:虽然代码能够正确地运行,但还可以将其划分为一系列完成具体工作的函数来进行改进。这样的过程被称为重构(refactor重构能让代码更清晰、更易于理解、更容易扩展104
10.4.310.4.3 重构重构让我们尝试对之前的 remember_me.py 进行重构,先回顾一下:remember_me.pyfrom pathlib import Pathimport jsonpath = Path('username.json')if path.exists(): contents = path.read_text() username = json.loads(contents) print(f"Welcome back, {username}!")else: username = input("What is your name? ") contents = json.dumps(username) path.write_text(contents) print(f"We'll remember you when you come back, {username}!")105
10.4.310.4.3 重构重构remember_me.py...def greet_user(): path = Path('username.json') if path.exists(): ... else: ...greet_user()我们首先考虑到,模块的主要功能是问候用户,所以就将几乎所有代码整理到了函数 greet_user 106

```python

from pathlib import Path

import json

def greet_user():

"""问候用户,并指出其名字"""

path = Path('username.json')

if path.exists():

contents = path.read_text()

username = json.loads(contents)

print(f"Welcome back, {username}!")

else:

username = input("What is your name? ")

contents = json.dumps(username)

path.write_text(contents)

print(f"We'll remember you when you come back, {username}!")

greet_user()

```

10.4.310.4.3 重构重构remember_me.py...def greet_user(): """计算一个文件大致包含多少个单词""" path = Path('username.json') if path.exists(): ... else: ...greet_user()我们还可以给函数增加文档字符串,来说明其作用107
10.4.310.4.3 重构重构remember_me.py...def greet_user(): """计算一个文件大致包含多少个单词""" path = Path('username.json') if path.exists(): ... else: ...greet_user()函数中的代码包含的代码是不是太多了?108

事实上来说,这不算多的,不过这里的主要目的,是为了简单演示重构的思路。

10.4.310.4.3 重构重构def get_store_username(): """如果存储了用户名,就获取它""" if path.exists(): contents = path.read_text() username = json.loads(contents) return username else: return None为此解决上一页提出的问题,我们首先将获取已存储用户名的代码进行整理,并将它们放入一个新创建的函数中去:109
10.4.310.4.3 重构重构再用新函数重构 greet_user 函数:remember_me.py...def greet_user(): """计算一个文件大致包含多少个单词""" path = Path('username.json') username = get_store_username(path) if username: print(f"Welcome back, {username}!") else: ...greet_user()110

```python

from pathlib import Path import json def get_stored_username(path): """如果存储了用户名,就获取它""" if path.exists(): contents = path.read_text() username = json.loads(contents) return username else: return None def greet_user(): """问候用户,并指出其名字""" path = Path('username.json') username = get_stored_username(path) if username: print(f"Welcome back, {username}!") else: username = input("What is your name? ") contents = json.dumps(username) path.write_text(contents) print(f"We'll remember you when you come back, {username}!") greet_user()

```

10.4.310.4.3 重构重构def get_new_username(path): """提示用户输入用户名""" username = input("What is your name? ") contents = json.dumps(username) path.write_text(contents) return username继续将 greet_user 函数中的另一个代码块提取出来,当没有存储用户名时提示用户输入的代码放在一个独立的函数中:111
10.4.310.4.3 重构重构继续重构 greet_user 函数:remember_me.py...def greet_user(): """计算一个文件大致包含多少个单词""" ... if username: print(f"Welcome back, {username}!") else: username = get_new_username(path) print(f"We'll remember you when you come back, {username}!")greet_user()重构到此为止,简单再过一遍112

```python

from pathlib import Path import json def get_stored_username(path): """如果存储了用户名,就获取它""" if path.exists(): contents = path.read_text() username = json.loads(contents) return username else: return None def get_new_username(path): """提示用户输入用户名""" username = input("What is your name? ") contents = json.dumps(username) path.write_text(contents) return username def greet_user(): """问候用户,并指出其名字""" path = Path('username.json') username = get_stored_username(path) if username: print(f"Welcome back, {username}!") else: username = get_new_username(path) print(f"We'll remember you when you come back, {username}!") greet_user()

```

10.4.310.4.3 重构重构针对最初的代码,我们首先将代码重构到了 greet_user 函数:def greet_user(): """问候用户,并指出其名字""" path = Path('username.json') if path.exists(): contents = path.read_text() username = json.loads(contents) print(f"Welcome back, {username}!") else: username = input("What is your name? ") contents = json.dumps(username) path.write_text(contents) print(f"We'll remember you when you come back, {username}!")113
10.4.310.4.3 重构重构接下来额外定义了两个专门的函数,以减轻其负担:def get_stored_username(path): """如果存储了用户名,就获取它""" if path.exists(): contents = path.read_text() username = json.loads(contents) return username else: return Nonedef get_new_username(path): """提示用户输入用户名""" username = input("What is your name? ") contents = json.dumps(username) path.write_text(contents) return username114
10.4.310.4.3 重构重构根据两个新函数,我们的 greet_user 函数得以简化:def get_stored_username(path): ... def get_new_username(path): ...def greet_user(): """问候用户,并指出其名字""" path = Path('username.json’) username = get_stored_username(path) if username: print(f"Welcome back, {username}!") else: username = get_new_username(path) print(f"We'll remember you when you come back, {username}!")115

进阶补充:如果我们编写的代码将涉及更深的嵌套,那么还能通过使用“卫语句”进一步简化。

greet_user 函数没有那么复杂,但同样能进行简化。

10.4.310.4.3 重构重构remember_me.py...def get_stored_username(path): ... def get_new_username(path): ...def greet_user(): ...greet_user()重构后的最终版本,每个函数都将执行单一而清晰的任务!要编写出清晰且易于维护和扩展的代码,重构是必不可少的。116
10.510.5 小结小结学习了如何使用文件,包括如何读取整个文件,如何读取文件中的各行,以及如何根据需要将任意数量的文本写入文件学习了异常,以及如何处理程序可能引发的异常如何存储 Python 数据结构,以保存用户提供的信息,避免让用户在每次运行程序时都重新提供。学习了如何重构代码,使得代码更加易于维护和扩展。在下一章中,我们将学习高效的代码测试方式。这不仅能帮助你确定代码正确无误,还有助于发现扩展既有程序时可能引入的bug117
课后拓展课后拓展学习 finally 语句的使用了解常见的编码类型,尝试基于不同的编码类型创建和读取文件可选拓展了解 Python 中的原始字符串概念,学习它的使用场景学习 with 语句块、内置函数 open(),并尝试结合使用它们了解并学习更多 except 的语法:学习如何为捕获到的异常设置别名,以及如何捕获多个异常学习如何捕获所有异常,并学习如何打印出捕获的任意异常了解 Python 异常组的相关内容118