36
在变量pi_string 存储的字符串中,包含原来位于每行左边的空格,为删除这些空格,可使用strip() 而不是rstrip() :
filename = 'pi_30_digits.txt'
with open(filename) as file_object:
lines = file_object.readlines()
pi_string = ''
for line in lines:
pi_string += line.strip()
print(pi_string)
print(len(pi_string))
这样,我们就获得了一个这样的字符串:它包含精确到30位小数的圆周率值。这个字符串长32字符,因为它还包含整数部分的3和小数点:
3.141592653589793238462643383279
32
注意 读取文本文件时,Python将其中的所有文本都解读为字符串。如果你读取的是数字,并要将其作为数值使用,就必须使用函数int() 将其转换为整数,或使用
函数float() 将其转换为浮点数。
10.1.6 包含一百万位的大型文件
前面我们分析的都是一个只有三行的文本文件,但这些代码示例也可处理大得多的文件。如果我们有一个文本文件,其中包含精确到小数点后1 000 000位而不是30位的圆周率
值,也可创建一个包含所有这些数字的字符串。为此,我们无需对前面的程序做任何修改,只需将这个文件传递给它即可。在这里,我们只打印到小数点后50位,以免终端为显
示全部1 000 000位而不断地翻滚:
pi_string.py
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()
print(pi_string[:52] + "…")
print(len(pi_string))
输出表明,我们创建的字符串确实包含精确到小数点后1 000 000位的圆周率值:
3.14159265358979323846264338327950288419716939937510…
1000002
对于你可处理的数据量,Python没有任何限制;只要系统的内存足够多,你想处理多少数据都可以。
注意 要运行这个程序(以及后面的众多示例),你需要从https://www.nostarch.com/pythoncrashcourse/ 下载相关的资源。
10.1.7 圆周率值中包含你的生日吗
我一直想知道自己的生日是否包含在圆周率值中。下面来扩展刚才编写的程序,以确定某个人的生日是否包含在圆周率值的前1 000 000位中。为此,可将生日表示为一个由数字
组成的字符串,再检查这个字符串是否包含在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.") 在❶处,我们提示用户输入其生日,在接下来的❷处,我们检查这个字符串是否包含在pi_string 中。运行一下这个程序:
Enter your birthdate, in the form mmddyy: 120372
Your birthday appears in the first million digits of pi!
我的生日确实出现在了圆周率值中!读取文件的内容后,就可以以你能想到的任何方式对其进行分析。
动手试一试
10-1 Python 学习笔记 :在文本编辑器中新建一个文件,写几句话来总结一下你至此学到的Python知识,其中每一行都以“In Python you can”打头。将这个文件命名为
learning_python.txt,并将其存储到为完成本章练习而编写的程序所在的目录中。编写一个程序,它读取这个文件,并将你所写的内容打印三次:第一次打印时读取整个
文件;第二次打印时遍历文件对象;第三次打印时将各行存储在一个列表中,再在with 代码块外打印它们。
10-2 C 语言学习笔记 :可使用方法replace() 将字符串中的特定单词都替换为另一个单词。下面是一个简单的示例,演示了如何将句子中的'dog' 替换为'cat'
:
>>> message = "I really like dogs."
>>> message.replace('dog', 'cat')
'I really like cats.'
读取你刚创建的文件learning_python.txt中的每一行,将其中的Python都替换为另一门语言的名称,如C。将修改后的各行都打印到屏幕上。
10.2 写入文件
保存数据的最简单的方式之一是将其写入到文件中。通过将输出写入文件,即便关闭包含程序输出的终端窗口,这些输出也依然存在:你可以在程序结束运行后查看这些输出,
可与别人分享输出文件,还可编写程序来将这些输出读取到内存中并进行处理。
10.2.1 写入空文件
要将文本写入文件,你在调用open() 时需要提供另一个实参,告诉Python你要写入打开的文件。为明白其中的工作原理,我们来将一条简单的消息存储到文件中,而不是将其打
印到屏幕上:
write_message.py
filename = 'programming.txt'
❶ with open(filename, 'w') as file_object:
❷ file_object.write("I love programming.")
在这个示例中,调用open() 时提供了两个实参(见❶)。第一个实参也是要打开的文件的名称;第二个实参('w' )告诉Python,我们要以写入模式 打开这个文件。打开文件
时,可指定读取模式 ('r' )、写入模式 ('w' )、附加模式 ('a' )或让你能够读取和写入文件的模式('r+' )。如果你省略了模式实参,Python将以默认的只读模式打
开文件。
如果你要写入的文件不存在,函数open() 将自动创建它。然而,以写入('w' )模式打开文件时千万要小心,因为如果指定的文件已经存在,Python将在返回文件对象前清空
该文件。
在❷处,我们使用文件对象的方法write() 将一个字符串写入文件。这个程序没有终端输出,但如果你打开文件programming.txt,将看到其中包含如下一行内容:
programming.txt
I love programming.
相比于你的计算机中的其他文件,这个文件没有什么不同。你可以打开它、在其中输入新文本、复制其内容、将内容粘贴到其中等。
注意 Python只能将字符串写入文本文件。要将数值数据存储到文本文件中,必须先使用函数str() 将其转换为字符串格式。
10.2.2 写入多行
函数write() 不会在你写入的文本末尾添加换行符,因此如果你写入多行时没有指定换行符,文件看起来可能不是你希望的那样:
filename = 'programming.txt'
with open(filename, 'w') as file_object:
file_object.write("I love programming.")
file_object.write("I love creating new games.")
如果你打开programming.txt,将发现两行内容挤在一起:
I love programming.I love creating new games.
要让每个字符串都单独占一行,需要在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")
现在,输出出现在不同行中:
I love programming.
I love creating new games.
像显示到终端的输出一样,还可以使用空格、制表符和空行来设置这些输出的格式。
10.2.3 附加到文件
如果你要给文件添加内容,而不是覆盖原有的内容,可以附加模式 打开文件。你以附加模式打开文件时,Python不会在返回文件对象前清空文件,而你写入到文件的行都将添加
到文件末尾。如果指定的文件不存在,Python将为你创建一个空文件。
下面来修改write_message.py,在既有文件programming.txt中再添加一些你酷爱编程的原因:
write_message.py
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") 在❶处,我们打开文件时指定了实参'a' ,以便将内容附加到文件末尾,而不是覆盖文件原来的内容。在❷处,我们又写入了两行,它们被添加到文件programming.txt末尾:
programming.txt
I love programming.
I love creating new games.
I also love finding meaning in large datasets.
I love creating apps that can run in a browser.
最终的结果是,文件原来的内容还在,它们后面是我们刚添加的内容。
动手试一试
10-3 访客 :编写一个程序,提示用户输入其名字;用户作出响应后,将其名字写入到文件guest.txt中。
10-4 访客名单 :编写一个while 循环,提示用户输入其名字。用户输入其名字后,在屏幕上打印一句问候语,并将一条访问记录添加到文件guest_book.txt中。确保这
个文件中的每条记录都独占一行。
10-5 关于编程的调查 :编写一个while 循环,询问用户为何喜欢编程。每当用户输入一个原因后,都将其添加到一个存储所有原因的文件中。
10.3 异常
Python使用被称为异常 的特殊对象来管理程序执行期间发生的错误。每当发生让Python不知所措的错误时,它都会创建一个异常对象。如果你编写了处理该异常的代码,程序将继
续运行;如果你未对异常进行处理,程序将停止,并显示一个traceback,其中包含有关异常的报告。
异常是使用try-except 代码块处理的。try-except 代码块让Python执行指定的操作,同时告诉Python发生异常时怎么办。使用了try-except 代码块时,即便出现异常,
程序也将继续运行:显示你编写的友好的错误消息,而不是令用户迷惑的traceback。
10.3.1 处理ZeroDivisionError 异常
下面来看一种导致Python引发异常的简单错误。你可能知道不能将一个数字除以0,但我们还是让Python这样做吧:
division.py
print(5/0)
显然,Python无法这样做,因此你将看到一个traceback:
Traceback (most recent call last):
File "division.py", line 1, in <module>
print(5/0)
❶ ZeroDivisionError: division by zero
在上述traceback中,❶处指出的错误ZeroDivisionError 是一个异常对象。Python无法按你的要求做时,就会创建这种对象。在这种情况下,Python将停止运行程序,并指出
引发了哪种异常,而我们可根据这些信息对程序进行修改。下面我们将告诉Python,发生这种错误时怎么办;这样,如果再次发生这样的错误,我们就有备无患了。
10.3.2 使用try-except 代码块
当你认为可能发生了错误时,可编写一个try-except 代码块来处理可能引发的异常。你让Python尝试运行一些代码,并告诉它如果这些代码引发了指定的异常,该怎么办。
处理ZeroDivisionError 异常的try-except 代码块类似于下面这样:
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:
You can't divide by zero!
如果try-except 代码块后面还有其他代码,程序将接着运行,因为已经告诉了Python如何处理这种错误。下面来看一个捕获错误后程序将继续运行的示例。
10.3.3 使用异常避免崩溃
发生错误时,如果程序还有工作没有完成,妥善地处理错误就尤其重要。这种情况经常会出现在要求用户提供输入的程序中;如果程序能够妥善地处理无效输入,就能再提示用
户提供有效输入,而不至于崩溃。
下面来创建一个只执行除法运算的简单计算器:
division.py
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
程序崩溃可不好,但让用户看到traceback也不是好主意。不懂技术的用户会被它们搞糊涂,而且如果用户怀有恶意,他会通过traceback获悉你不希望他知道的信息。例如,他将知
道你的程序文件的名称,还将看到部分不能正确运行的代码。有时候,训练有素的攻击者可根据这些信息判断出可对你的代码发起什么样的攻击。
10.3.4 else 代码块
通过将可能引发错误的代码放在try-except 代码块中,可提高这个程序抵御错误的能力。错误是执行除法运算的代码行导致的,因此我们需要将它放到try-except 代码块
中。这个示例还包含一个else 代码块;依赖于try 代码块成功执行的代码都应放到else 代码块中:
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: ")
❶ try:
answer = int(first_number) / int(second_number)
❷ except ZeroDivisionError:
print("You can't divide by 0!")
❸ else:
print(answer)
我们让Python尝试执行try 代码块中的除法运算(见❶),这个代码块只包含可能导致错误的代码。依赖于try 代码块成功执行的代码都放在else 代码块中;在这个示例中,如
果除法运算成功,我们就使用else 代码块来打印结果(见❸)。
except 代码块告诉Python,出现ZeroDivisionError 异常时该怎么办(见❷)。如果try 代码块因除零错误而失败,我们就打印一条友好的消息,告诉用户如何避免这种错
误。程序将继续运行,用户根本看不到traceback:
Give me two numbers, and I'll divide them.
Enter 'q' to quit.
First number: 5
Second number: 0
You can't divide by 0!
First number: 5
Second number: 2