Python编程:从入门到实践(学习笔记-Charpter 10)

Python编程:从入门到实践(学习笔记-Charpter 10)

十月 17, 2019

Chapter 10:文件和异常

10.1从文件中读取数据

10.1.1 读取整个文件

pi_digits.txt

1
2
3
3.1415926535
8979323846
2643383279

file_reader.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
with open('pi_digits.txt') as file_object:
contents = file_object.read()
print(contents)
print(contents.rstrip()) #删除多出来的空行

'''
1.要以任何方式使用文件,哪怕仅仅是打印其内容,都得先打开这个文件,才能访问它

2.在这个实例中,当前运行的是file_reader.py,因此Python在file_reader.py所在的目录中查找pi_digits.txt.

3.在这个程序中,调用了open(),没有调用close();你也可以调用open()和close()来打开和关闭文件,但这样做时,如果程序存在bug,导致close()语句未执行,文件将不会关闭,这看似微不足道,但未妥善地关闭文件可能会导致数据丢失或受损。如果在程序中过早地调用 close() ,你会发现需要使用文件时它已关闭 (无法访问),这会导致更多的错误。并非在任何情况下都能轻松确定关闭文件的恰当时机,但通过使用前面所示的结构,可让 Python去确定:你只管打开文件,并在需要时使用它,Python 自会在合适的时候自动将其关闭。
'''
# open()函数,接受一个参数:要打开的文件名称
# with关键字,在不在需要访问文件后将其关闭
# read()函数,读取文件的全部内容,并将其作为一个长长的字符串存储在变量contents中。
10.1.2 文件路径

相对文件路径:让Python到指定的位置去找,该位置是相对于当前运行的程序所在目录的

1
with open('text_files/filename.txt') as file_object:

绝对文件路径:需要提供完整的路径

1
2
3
4
#绝对路径通常比较长,因此将其存储在一个变量中,再将该变量传递给open()会有所帮助。

file_path = '/home/ehmatthes/other_files/text_files/filename.txt'
with open(file_path) as file_object:

windows系统下:在文件路径中使用反斜杠 ( \ ) 而不是 斜杠( / )

1
2
3
4
5
6
#相对路径
with open('text_files\filename.txt') as file_object:

#绝对路径
file_path = 'C:\Users\ehmatthes\other_files\text_files\filename.txt'
with open(file_path) as file_object:
10.1.3逐行读取

file_reader.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#❶ 
filename = 'pi_digits.txt'

#❷
with open(filename) as file_object:
#❸
for line in file_object:
print(line)

'''
在❶处,我们将要读取的文件的名称存储在变量 filename 中,这是使用文件时一种常见的做法。由于变量filename表示的并非实际文件——它只是一个让Python知道到哪里去查找文件的字符串,因此可轻松地将'pi_digits.txt'替换为你要使用的另一个文件的名称。调用open()后,将一个表示文件及其内容的对象存储到了变量file_object中(见❷)。这里也使用了关键字 with ,让 Python 负责妥善地打开和关闭文件。为查看文件的内容,我们通过对文件对象执行循环来遍历文件中的每一行(见❸)。
'''

--------------------------
3.1415926535
8979323846
2643383279


#我们打印每一行时,发现空白行更多了:
#这个文件中,每行的末尾都有一个看不见的换行符,而 print 语句也会加上一个换行符,因此每行末尾都有两个换行符:一个来自文件,另一个来自 print 语句。要消除这些多余的空白行,可在 print 语句中使用 rstrip() :
print(line.rstrip())
10.1.4创建一个包含文件各行内容的列表
1
2
3
4
5
6
7
8
9
filename = 'pi_digits.txt'

with open(filename) as file_object:
lines = file_object.readlines() #❶

for line in lines: #❷
print(line.rstrip())

#❶处的方法 readlines() 从文件中读取每一行,并将其存储在一个列表中;接下来,该列表被存储到变量 lines 中;在 with 代码块外,我们依然可以使用这个变量。在❷处,我们使用一个简单的 for 循环来打印 lines 中的各行。由于列表 lines 的每个元素都对应于文件中的一行,因此输出与文件内容完全一致。
10.1.5 使用文件的内容

pi_string.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
filename = 'pi_digits.txt'

with open(filename) as file_object:
lines = file_object.readlines()


pi_string = '' #❶
for line in lines: #❷
pi_string += line.strip() #为删除这些空格,可使用 strip() 而不是 rstrip()

print(pi_string) #❸
print(len(pi_string))

#在❶处,我们创建了一个变量 —— pi_string ,用于存储圆周率的值。接下来,我们使用一个循环将各行都加入 pi_string ,并删除每行末尾的换行符(见❷)。在❸处,我们打印这个字符串及其长度:

注意 读取文本文件时, Python 将其中的所有文本都解读为字符串。如果你读取的是数字,并要将其作为数值使用,就必须使用函数 int() 将其转换为整数,或使用函数 float() 将其转换为浮点数。

10.1.6 包含一百万位的大型文件

pi_string.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
'''前面分析的都是一个只有三行的文本文件,但这些代码示例也可处理大得多的文件'''

filename = 'pi_million_digits.txt'

with open(filename) as file_object:
lines = file_object.readlines()


pi_string = ''
for line in lines:
pi_string += line.strip() #为删除这些空格,可使用 strip() 而不是 rstrip()

print(pi_string[:52] + '...') #打印到小数点后 50 位
print(len(pi_string))
10.1.7圆周率值中包含你的生日吗
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
'''可将生日表示为一个由数字组成的字符串,再检查这个字符串是否包含在 pi_string中:'''

filename = 'pi_million_digits.txt'
with open(filename) as file_object:
lines = file_object.readlines()

pi_string = ''
for line in lines:
pi_string += line.rstrip()

#❶
birthday = input("Enter your birthday, in the form mmddyy: ")
#❷
if birthday in pi_string:
print("Your birthday appears in the first million digits of pi!")
else:
print("Your birthday does not appear in the first million digits of pi.")

10.2 写入文件

open(filename, ‘w‘) : 写入模式

open(filename, ‘r‘): 读取模式

open(filename, ‘a‘): 附加模式

open(filename, ‘r+‘): 读取和写入模式

10.2.1写入空文件

write_message.py

1
2
3
4
5
6
7
8
9
10
filename = 'programming.txt'

with open(filename, 'w') as file_object: #❶
file_object.write("I love programming.") #❷

'''
在这个示例中,调用 open() 时提供了两个实参(见❶)。
第一个实参也是要打开的文件的名称;第二个实参( 'w' )告诉 Python ,我们要以写入模式 打开这个文件。
在❷处,我们使用文件对象的方法 write() 将一个字符串写入文件。这个程序没有终端输出,但如果你打开文件 programming.txt ,将看到其中包含如下一行内容:
'''

如果你要写入的文件不存在,函数 open() 将自动创建它。然而,以写入( ‘w’ )模式打开文件时千万要小心,因为如果指定的文件已经存在,,Python 将在返回文件对象前清空该文件。

10.2.2 写入多行
1
2
3
4
5
#要让每个字符串都单独占一行,需要在 write() 语句中包含换行符:
filename = 'programming.txt'
with open(filename, 'w') as file_object:
file_object.write("I love programming.\n")
file_object.write("I love creating new games.\n")
10.2.3 附加到文件

如果你要给文件添加内容,而不是覆盖原有的内容,可以附加模式 打开文件。你以附加模式打开文件时, Python 不会在返回文件对象前清空文件,而你写入到文件的行都将添加到文件末尾。如果指定的文件不存在, Python 将为你创建一个空文件。

1
2
3
4
5
6
filename = 'programming.txt'


with open(filename, 'a') as file_object: #❶
file_object.write("I also love finding meaning in large datasets.\n") #❷
file_object.write("I love creating apps that can run in a browser.\n")

10.3 异常

异常是使用 try-except 代码块处理的。 try-except 代码块让 Python 执行指定的操作,同时告诉 Python 发生异常时怎么办。使用了 try-except 代码块时,即便出现异常,程序也将继续运行:显示你编写的友好的错误消息,而不是令用户迷惑的 traceback 。

10.3.1 处理 ZeroDivisionError 异常
10.3.2 使用try-except代码块
1
2
3
4
5
6
7
try:
print(5/0)
except ZeroDivisionError:
print("You can't divide by zero!")

#将导致错误的代码行 print(5/0) 放在了一个 try 代码块中。如果 try 代码块中的代码运行起来没有问题, Python 将跳过 except 代码块;如果 try 代码块中的代码导致了错误, Python 将查找这样的 except 代码块,并运行其中的代码,即其中指定的错误与引发的错误相同。
#在这个示例中, try 代码块中的代码引发了 ZeroDivisionError 异常,因此 Python 指出了该如何解决问题的 except 代码块,并运行其中的代码。这样,用户看到的是一条友好的错误消息,而不是 traceback :

如果 try-except 代码块后面还有其他代码,程序将接着运行,因为已经告诉了 Python 如何处理这种错误。

10.3.3使用异常避免崩溃

division.py

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
print("Give me two numbers, and I'll divide them.")
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)

#这个程序提示用户输入一个数字,并将其存储到变量first_number中;如果用户输入的不是表示退出的q,就再提示用户输入一个数字,并将其存储到变量second_number中(见❷)。接下来,我们计算这两个数字的商(即answer,见❸)。这个程序没有采取任何处理错误的措施,因此让它执行除数为 0 的除法运算时,它将崩溃:

-----------------------------------------
Give me two numbers, and I'll divide them.
Enter 'q' to quit.

First number: 5
Second number: 0
Traceback (most recent call last):
File "division.py", line 9, in <module>
answer = int(first_number) / int(second_number)
ZeroDivisionError: division by zero

:ballot_box_with_check: 程序崩溃可不好,但让用户看到 traceback 也不是好主意。不懂技术的用户会被它们搞糊涂,而且如果用户怀有恶意,他会通过 traceback 获悉你不希望他知道的信息。例如,他将知道你的程序文件的名称,还将看到部分不能正确运行的代码。有时候,训练有素的攻击者可根据这些信息判断出可对你的代码发起什么样的攻击。

10.3.4 else代码块

通过将可能引发错误的代码放在 try-except 代码块中,可提高这个程序抵御错误的能力

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
print("Give me two numbers, and I'll divide them.")
print("Enter 'q' to quit.")

while True:
first_number = input("\nFirst number: ")
if first_number == 'q':
break
second_number = input("\nSecond number: ")

try:
answer = int(first_nubmer)/int(second_number)
except ZeroDivisionError:
print("you can't divide by 0!")
else:
print(answer)

#在这个示例中,如果除法运算成功,我们就使用 else 代码块来打印结果
#如果try代码块因除零错误而失败,我们就打印一条友好的消息,告诉用户如何避免这种错误。程序将继续运行,用户根本看不到traceback

try-except-else 代码块的工作原理大致如下:

Python 尝试执行 try 代码块中的代码;只有可能引发异常的代码才需要放在 try 语句中。

有时候,有一些仅在 try 代码块成功执行时才需要运行的代码;这些代码应放在 else 代码块中。

except 代码块告诉 Python ,如果它尝试运行 try 代码块中的代码时引发了指定的异常,该怎么办。

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

10.3.5 处理FileNoteFoundError异常

使用文件时,一种常见的问题是找不到文件:你要查找的文件可能在其他地方、文件名可能不正确或者这个文件根本就不存在。对于所有这些情形,都可使用 try-except 代码块以直观的方式进行处理。

alice.py

1
2
3
4
5
6
7
8
9
filename = 'alice.txt'
with open(filename) as f_obj:
contents = f_obj.read()

#Python 无法读取不存在的文件,因此它引发一个异常:
Traceback (most recent call last):
File "alice.py", line 3, in <module>
with open(filename) as f_obj:
FileNotFoundError: [Errno 2] No such file or directory: 'alice.txt'

在这个示例中,这个错误是函数open()导致的,因此要处理这个错误,必须将try语句放在包含 open() 的代码行之前:

1
2
3
4
5
6
7
8
9
10
filename = 'alice.txt'

try:
with open(filename) as f_obj:
contents = f_obj.read()
except FileNotFoundError:
msg = "sorry, the file " + filename + "does not exist."
print(msg)

#try代码块引发FileNotFoundError异常,因此Python找出与该错误匹配的except代码块,并运行其中的代码。最终的结果是显示一条友好的错误消息,而不是traceback:
10.3.6 分析文本

下面来提取童话 Alice in Wonderland 的文本,并尝试计算它包含多少个单词。我们将使用方法 split(),它根据一个字符串创建一个单词列表。下面是对只包含童话名 “Alice in Wonderland” 的字符串调用方法 split()的结果:

1
2
3
>>> title = "Alice in Wonderland"
>>> title.split()
['Alice', 'in', 'Wonderland']

方法 split() 以空格为分隔符将字符串分拆成多个部分,并将这些部分都存储到一个列表中。结果是一个包含字符串中所有单词的列表,虽然有些单词可能包含标点。为计算Alice in Wonderland 包含多少个单词,我们将对整篇小说调用split() ,再计算得到的列表包含多少个元素,从而确定整篇童话大致包含多少个单词:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
filename = 'alice.txt'

try:
with open(filename) as f_obj:
contents = f_obj.read()
except FileNotFoundError:
msg = "sorry, the file " + filename + "does not exist."
print(msg)
else:
#计算文件大致包含多少个单词
words = contents.split() # ❶
num_words = len(words) # ❷
msg2 = "The file " + filename + "has about " + str(num_words) + " word."
print(msg2) # ❸

#在❶处,我们对变量 contents (它现在是一个长长的字符串,包含童话 Alice in Wonderland 的全部文本)调用方法 split() ,以生成一个列表,其中包含这部童话中的所有单词。当我们使用 len() 来确定这个列表的长度时,就知道了原始字符串大致包含多少个单词(见❷)。在❸处,我们打印一条消息,指出文件包含多少个单词。这些代码都放在 else 代码块中,因为仅当 try 代码块成功执行时才执行它们。
10.3.7 使用多个文件

word_count.py

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
def count_word(filename):
"""计算一个文本大致包含多少单词"""
try:
with open(filename) as f_obj:
contents = f_obj.read()
except FileNotFoundError:
msg = "sorry, the file " + filename + "does not exist."
print(msg)

else:
#计算文件大致包含多少个单词
words = contents.split()
num_words = len(words)
msg2 = "The file " + filename + "has about " + str(num_words) + " word."
print(msg2)

------调用函数-----
filenames = ['alice.txt', 'siddhartha.txt', 'moby_dick.txt', 'little_women.txt']
for filename in filenames:
count_words(filename)

------
The file alice.txt has about 29461 words.
Sorry, the file siddhartha.txt does not exist.
The file moby_dick.txt has about 215136 words.
The file little_women.txt has about 189079 words.
10.3.8 失败时一声不吭 ( pass )
1
2
3
4
5
6
7
8
9
#Python 有一个 pass 语句,可在代码块中使用它来让 Python 什么都不要做:
def count_words(filename):
""" 计算一个文件大致包含多少个单词 """
try:
--snip--
except FileNotFoundError:
pass #错误发生时,不会有任何输出
else:
--snip--

pass 语句还充当了占位符,它提醒你在程序的某个地方什么都没有做,并且以后也许要在这里做些什么。例如,在这个程序中,我们可能决定将找不到的文件的名称写入到文件 missing_files.txt 中。用户看不到这个文件,但我们可以读取这个文件,进而处理所有文件找不到的问题。

10.3.9 决定报告哪些错误

在什么情况下该向用户报告错误?在什么情况下又应该在失败时一声不吭呢?如果用户知道要分析哪些文件,他们可能希望在有文件没有分析时出现一条消息,将其中的原因告诉他们。如果用户只想看到结果,而并不知道要分析哪些文件,可能就无需在有些文件不存在时告知他们。向用户显示他不想看到的信息可能会降低程序的可用性。Python 的错误处理结构让你能够细致地控制与用户分享错误信息的程度,要分享多少信息由你决定。

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

10.4 存储数据

很多程序都要求用户输入某种信息,如让用户存储游戏首选项或提供要可视化的数据。不管专注的是什么,程序都把用户提供的信息存储在列表和字典等数据结构中。用户关闭程序时,你几乎总是要保存他们提供的信息;一种简单的方式是使用模块 json 来存储数据。

模块 json 让你能够将简单的 Python 数据结构转储到文件中,并在程序再次运行时加载该文件中的数据。你还可以使用 json 在 Python 程序之间分享数据。更重要的是, JSON 数据格式并非 Python 专用的,这让你能够将以 JSON 格式存储的数据与使用其他编程语言的人分享。这是一种轻便格式,很有用,也易于学习。

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

10.4.1 使用 json.dump() 和 json.load()

我们来编写一个存储一组数字的简短程序,再编写一个将这些数字读取到内存中的程序。第一个程序将使用 json.dump() 来存储这组数字,而第二个程序将使用 json.load() 。

number_writer.py

1
2
3
4
5
6
7
8
9
10
11
#函数 json.dump() 接受两个实参:要存储的数据 以及可用于存储数据的文件对象

import json

numbers = [2, 3, 5, 7, 11, 13]

filename = 'number.json'
with open(filename, 'w') as f_obj:
json.dump(numbers, f_obj)

#我们先导入模块json ,再创建一个数字列表。在❶处,我们指定了要将该数字列表存储到其中的文件的名称。通常使用文件扩展名.json来指出文件存储的数据为JSON格式。接下来,我们以写入模式打开这个文件,让json能够将数据写入其中(见❷)。在❸处,我们使用函数 json.dump()将数字列表存储到文件numbers.json中。

number_reader.py

1
2
3
4
5
6
7
8
9
10
import json

filename = 'numbers.json' #❶
with open(filename) as f_obj: #❷
numbers = json.load(f_obj) #❸

print(numbers)


#在❶处,我们确保读取的是前面写入的文件。这次我们以读取方式打开这个文件,因为 Python 只需读取这个文件(见❷)。在❸处,我们使用函数 json.load() 加载存储在numbers.json 中的信息,并将其存储到变量 numbers 中。最后,我们打印恢复的数字列表
10.4.2 保存和读取用户生成的数据

remember_me.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import json

#如果以前存储了用户名,就加载它
#否则,就提示用户输入用户名并存储它
filename = 'username.json'
try:
with open(filename) as f_obj: #❶
username = json.load(f_obj) #❷
except FileNotFoundError: #❸
username = input("What's is your name?") #❹
with open(filename, 'w') as f_obj: #❺
json.dump(username, f_obj)
print("We'll remember you when you come back, " + username + "!")
else:
print("Welcome back, " + username + "!")

#在❶处,我们尝试打开文件 username.json 。如果这个文件存在,就将其中的用户名读取到内存中(见❷),再执行 else 代码块,即打印一条欢迎用户回来的消息。用户首次运行这个程序时,文件 username.json 不存在,将引发 FileNotFoundError 异常(见❸),因此 Python 将执行 except 代码块:提示用户输入其用户名(见❹),再使用 json.dump() 存储该用户名,并打印一句问候语(见❺)。无论执行的是 except 代码块还是 else 代码块,都将显示用户名和合适的问候语。
10.4.3 重构

你经常会遇到这样的情况:代码能够正确地运行,但可做进一步的改进 —— 将代码划分为一系列完成具体工作的函数。这样的过程被称为重构 。重构让代码更清晰、更易于理解、更容易扩展。

重构 remember_me.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import json

def greet_user():
"""问候用户,并指出其名字"""
filename = 'username.json'
try:
with open(filename) as f_obj:
username = json.load(f_obj)
except FileNotFoundError:
username = input("What is your name?")
with open(filename, 'w') as f_obj:
json.dump(username, f_obj)
print("We'll remember you when you come back, " + username + "!")
else:
print("Welcome back, " + username + "!")

greet_user()

重构greet_user()

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
import json

def get_stored_username():
"""如果存储了用户,就获取它"""
filename = 'username.json'
try:
with open(filename) as f_obj:
username = json.load(f_obj)
except FileNotFoundError:
return None
else:
return username

def greet_user():
"""问候用户,并指出其名字"""
username = get_stored_username()
if username:
print("Welcome back, " + username + "!")
else:
username = input("What is your name?")
filename = 'username,json'
with open(filename, 'w') as f_obj:
json.dump(username, f_obj)
print("We'll remember you when you come back, " + username + "!")

greet_user()

进一步优化

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
import json

def get_stored_username():
"""如果存储了用户,就获取它"""
filename = 'username.json'
try:
with open(filename) as f_obj:
username = json.load(f_obj)
except FileNorFoundError:
return None
else:
return username

def get_new_username():
"""提示用户输入用户名"""
username = input("What's your name?")
filename = 'username.json'
with open(filename, 'w') as f_obj:
json.dump(username, f_obj)
return username

def greet_user():
"""问候用户,并指出其名字"""
username = get_stored_username()
if username:
print("Welcome back, " + username + "!")
else:
username = get_new_username()
print("We'll remember you when you come back, " + username + "!")

greet_user()

##