课前导言
在本章中,你将学习处理文件,让程序能够快速地分析大量的数据;你将学习错误处理,避免程序在面对意外情形时崩溃;你将学习异常,它们是Python创建的特殊对象,用于管理程序运行时出现的错误;你还将学习模块json,它让你能够保存用户数据,以免在程序停止运行后丢失。
OK 开始今天的内容吧
从文件中读取数据
读取整个文件
要读取文件,需要一个包含几行文本的文件。
下面首先来创建一个文件,它包含精确到小数点后30位的圆周率值,且在小数点后每10位处都换行:
with open('pi_digits') as file_object:
contents=file_object.read()
print(contents.rstrip())
文件路径
with open('text_files/fileman.text') as file_object: #在Linus和OSX中
with open('text_files\fileman.text') as file_object: #在Windows中
#在Linus和OSX中
file_path='/home/ehmatthes/other_files/text_files/filename.text'
with open(file_path) as file_object:
#在Windows中
file_path='C:\Users\ehmatthes\other_files\text_files\filename.text'
with open(file_path) as file_object:
filename='pi_digits.text'#将要读取的文件名称存储在变量filename中
with open(filename) as file_object:#这里使用了with,让python负责妥善地打开和关闭文件
for line in file_object:
print(line)
- 打印出地每一行下面都有两行空白行:在这个文件中,每行的末尾都有一个看不见的换行符,而print语句也会加上一个换行符,因此每行末尾都有两个换行符(一个来自文件,一个来自print语句)
- 要消除这些多余的空白行,可在print语句中使用
rstrip()
:
print(line.rstrip())
创建一个包含文件各行内容的列表
- 使用关键字with时,open()返回的文件对象只在with代码块内可用。
- 使用关键字with时,open()返回的文件对象只在with代码块内可用。
filename='pi_digits'
with open(filename) as file_object:
lines=file_object.readlines()
for line in lines:
print(line.rstrip())
- 方法readlines()从文件中读取每一行,并将其存储在一个列表中
- 接下来,该列表被存储到变量lines中
- 在with代码块外,我们依然可以使用这个变量
使用文件的内容
将文件读取到内存中后,就可以以任何方式使用这些数据了。下面以简单的方式使用圆周率的值。首先,我们将创建一个字符串,它包含文件中存储的所有数字,且没有任何空格:
filename='pi_digits'
with open(filename) as file_object:
lines=file_object.readlines()
pi_string=''#用于存储圆周率的值
for line in lines:
pi_string+=line.rstrip()#将各行都加入pi_string,并删除每行末尾的换行符
print(pi_string)
print(len(pi_string))#打印长度
结果
3.1415926535 8979323846 2643383279
36
在变量pi_string存储的字符串中,包含原来位于每行左边的空格,为删除这些空格,可使用strip()而不是rstrip():
filename='pi_digits'
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))
结果
3.141592653589793238462643383279
32
读取文本文件时,Python将其中的所有文本都解读为字符串。
如果你读取的是数字,并要将其作为数值使用,就必须使用函数int()将其转换为整数,或使用函数float()将其转换为浮点数。
包含一百万位的大型文件
前面我们分析的都是一个只有三行的文本文件,但这些代码示例也可处理大得多的文件。如果我们有一个文本文件,其中包含精确到小数点后1000000位而不是30位的圆周率值,也可创建一个包含所有这些数字的字符串。为此,我们无需对前面的程序做任何修改,只需将这个文件传递给它即可。
(知道有这么回事就行了python对你要处理的数据量没有限制,全看你的内存了)
来个完整的例子(当然是书上的)
测测圆周率包不包含你的生日
filename='pi_digits'
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 firat million digits of pi.")
print(pi_string)
print(len(pi_string))
C语言学习笔记:可使用方法replace()将字符串中的特定单词都替换为另一个单词。
>>> message="l really like dogs"
>>> message.replace('dog','cat')
'l really like cats'
写入文件
保存数据的最简单的方式之一是将其写入到文件中。通过将输出写入文件,即便关闭包含程序输出的终端窗口,这些输出也依然存在:你可以在程序结束运行后查看这些输出,可与别人分享输出文件,还可编写程序来将这些输出读取到内存中并进行处理。(这个好啊,很有用!!)
写入空文件
先来例子,再讲概念!
filename='programming.text'
with open(filename,'w') as file_object:
file_object.write('L love programming!')
- open()提供了两个实参,第一个实参也是要打开的文件的名称,第二个实参是告诉python我们要以写入模式打开这个文件(有三种形式啦:‘r’——读取模式、‘w’——写入模式、‘a’——附加模式、‘r+’——能够读取和写入)【如果你省略了模式实参,Python将以默认的只读模式打开文件。】
- 如果你要写入的文件不存在,函数open()将自动创建它。
-以写入(‘w’)模式打开文件时千万要小心,因为如果指定的文件已经存在,Python将在返回文件对象前清空该文件。 - 在最后一行,我们使用文件对象的方法write()将一个字符串写入文件。这个程序没有终端输出,但如果你打开文件programming.txt,将看到其中包含如下一行内容:
L love programming
(相比于你的计算机中的其他文件,这个文件没有什么不同。你可以打开它、在其中输入新文本、复制其内容、将内容粘贴到其中等。)
Python只能将字符串写入文本文件。
要将数值数据存储到文本文件中,必须先使用函数str()将其转换为字符串格式。
写入多行
函数write()不会在你写入的文本末尾添加换行符,因此如果你写入多行时没有指定换行符,你所输入的句子会连在一起,要让每个字符串都单独占一行,需要在write()语句中包含换行符。
filename='programming.text'
with open(filename,'w') as file_object:
file_object.write('L love programming!\n')
file_object.write("L love creating new games\n")
结果:
L love programming!
l love creating new games
附加到文件
- 如果你要给文件添加内容,而不是覆盖原有的内容,可以附加模式打开文件。
- 你以附加模式打开文件时,Python不会在返回文件对象前清空文件,而你写入到文件的行都将添加到文件末尾。
- 如果指定的文件不存在,Python将为你创建一个空文件。
异常(了解一下就成了)
- Python使用被称为异常的特殊对象来管理程序执行期间发生的错误。
- 每当发生让Python不知所措的错误时,它都会创建一个异常对象。
- 如果你编写了处理该异常的代码,程序将继续运行;如果你未对异常进行处理,程序将停止,并显示一个traceback,其中包含有关异常的报告。
- 异常是使用try-except代码块处理的。
- try-except代码块让Python执行指定的操作,同时告诉Python发生异常时怎么办。
- 使用了try-except代码块时,即便出现异常,程序也将继续运行:显示你编写的友好的错误消息,而不是令用户迷惑的traceback。
处理ZeroDivisionError异常
假如你照下面这么干:
print(5/0)
显然,Python无法这样做,因此你将看到一个traceback:
Traceback (most recent call last):
File "D:/python不知道啥/helloworld.py", line 1, in <module>
print(5/0)
ZeroDivisionError: division by zero
使用try-except代码块
当你认为可能发生了错误时,可编写一个try-except代码块来处理可能引发的异常。你让Python尝试运行一些代码,并告诉它如果这些代码引发了指定的异常,该怎么办。
try:
print(5/0)
except ZeroDivisionError:
print("You can't divide by zero!")
我们将导致错误的代码行print(5/0)放在了一个try代码块中。如果try代码块中的代码运行起来没有问题,Python将跳过except代码块;如果try代码块中的代码导致了错误,Python将查找这样的except代码块,并运行其中的代码,即其中指定的错误与引发的错误相同。
如果try-except代码块后面还有其他代码,程序将接着运行,因为已经告诉了Python如何处理这种错误。(这个挺好玩的,但不知道实际操作时它能干啥用??)
使用异常避免崩溃
如果程序能够妥善地处理无效输入,就能再提示用户提供有效输入,而不至于崩溃。
下面来创建一个只执行除法运算的简单计算器:
print("Give me two numbers,and l will divide them.")
print("Enter 'q' to quit")
while True:
first_number=input("\nFirst number:")#1
if first_number=='q':
break
second_number=input("Second number:")#2
if second_number=='q':
break
answer=int(first_number)/int(second_number)#3
print(answer)
这个程序没有采取任何处理错误的措施,因此让它执行除数为0的除法运算时,它将崩溃
【程序崩溃可不好,但让用户看到traceback也不是好主意。不懂技术的用户会被它们搞糊涂,而且如果用户怀有恶意,他会通过traceback获悉你不希望他知道的信息。例如,他将知道你的程序文件的名称,还将看到部分不能正确运行的代码。有时候,训练有素的攻击者可根据这些信息判断出可对你的代码发起什么样的攻击。】(这么有用的么!!!!!)
else代码块
- 通过将可能引发错误的代码放在try-except代码块中,可提高这个程序抵御错误的能力。
- 错误是执行除法运算的代码行导致的,因此我们需要将它放到try-except代码块中。
- 这个示例还包含一个else代码块;依赖于try代码块成功执行的代码都应放到else代码块中
print("Give me two numbers,and l will divide them.")
print("Enter 'q' to quit")
while True:
first_number=input("\nFirst number:")#1
if first_number=='q':
break
second_number=input("Second number:")#2
if second_number=='q':
break
try:
answer=int(first_number)/int(second_number)
except ZeroDivisionError:
print("You can not divide by 0!")
else:
print(answer)
try-except-else代码块的工作原理大致如下:
- Python尝试执行try代码块中的代码;
- 只有可能引发异常的代码才需要放在try语句中。
处理FileNotFoundError异常
使用文件时,一种常见的问题是找不到文件:你要查找的文件可能在其他地方、文件名可能不正确或者这个文件根本就不存在。对于所有这些情形,都可使用try-except代码块以直观的方式进行处理。
我们来尝试读取一个不存在的文件。下面的程序尝试读取文件alice.txt的内容,但我没有将这个文件存储在alice.py所在的目录中
filename='alice.text'
with open(filename) as f_obj:
contents=f_obj.read()
Python无法读取不存在的文件,因此它引发一个异常
Traceback (most recent call last):
File "D:/python不知道啥/helloworld.py", line 2, in <module>
with open(filename) as f_obj:
FileNotFoundError: [Errno 2] No such file or directory: 'alice.text'
在上述traceback中,最后一行报告了FileNotFoundError异常,这是Python找不到要打开的文件时创建的异常。在这个示例中,这个错误是函数open()导致的,因此要处理这个错误,必须将try语句放在包含open()的代码行之前
filename='alice.text'
try:
with open(filename) as f_obj:
contents=f_obj.read()
except FileNotFoundError:
msg="Sorry,the file "+filename+" does not xeist."
print(msg)
分析文本
你可以分析包含整本书的文本文件。
很多经典文学作品都是以简单文本文件的方式提供的,因为它们不受版权限制。
本节使用的文本来自项目Gutenberg(http://gutenberg.org/),这个项目提供了一系列不受版权限制的文学作品,如果你要在编程项目中使用文学文本,这是一个很不错的资源。
(这个网站好酷啊,以后就拿他来阅读英文原版书了,好好玩的样子啊,收藏了!)
下面来提取童话Alice in Wonderland的文本,并尝试计算它包含多少个单词。我们将使用方法split(),它根据一个字符串创建一个单词列表。下面是对只包含童话名"Alice in Wonderland"的字符串调用方法split()的结果:
>>> title="Alice in Wonderland"
>>> title.split()
['Alice', 'in', 'Wonderland']
- 方法split()以空格为分隔符将字符串分拆成多个部分,并将这些部分都存储到一个列表中。
- 结果是一个包含字符串中所有单词的列表,虽然有些单词可能包含标点。
为计算Alice in Wonderland包含多少个单词,我们将对整篇小说调用split(),再计算得到的列表包含多少个元素,从而确定整篇童话大致包含多少个单词:
filename='alice.text'
try:
with open(filename) as f_obj:
contents=f_obj.read()
except FileNotFoundError:
msg="Sorry,the file "+filename+" does not xeist."
print(msg)
else:
#计算文件大致包含多少单词
words=contents.split()
num_words=len(words)
print("The file"+filename+" has about "+str(num_words)+" words")
[我没有真的去把文章下载下来,有点耗流量,等我清明回家再搞]
使用多个文件
下面多分析几本书。这样做之前,我们先将这个程序的大部分代码移到一个名为count_words()的函数中,这样对多本书进行分析时将更容易:
def count_words(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)
print("The file "+filename+" has about "+str(num_words)+"words")
filename='a.text'
count_words(filename)
现在可以编写一个简单的循环,计算要分析的任何文本包含多少个单词了。为此,我们将要分析的文件的名称存储在一个列表中,然后对列表中的每个文件都调用count_words()。
filenames=['a.text','b.text','siddhartha.txt','Moby Dick','LittleWomen']
for filename in filenames:
cout_words(filename)
文件siddhartha.txt不存在,但这丝毫不影响这个程序处理其他文件
在这个示例中,使用try-except代码块提供了两个重要的优点:避免让用户看到traceback;让程序能够继续分析能够找到的其他文件。
如果不捕获因找不到siddhartha.txt而引发的FileNotFoundError异常,用户将看到完整的traceback,而程序将在尝试分析Siddhartha后停止运行——根本不分析Moby Dick和LittleWomen。
失败时一声不吭
有时候你希望程序在发生异常时一声不吭,就像什么都没有发生一样继续运行。要让程序在失败时一声不吭,可像通常那样编写try代码块,但在except代码块中明确地告诉Python什么都不要做。Python有一个pass语句,可在代码块中使用它来让Python什么都不要做:
def count_words(filename):
"""计算一个文件大致包含多少个单词"""
try:
--snip--
expect FileNotFoundError:
pass
else:
--snip--
filenames=['a.text','s.text','m.text']
for filename in filenames:
count_words(filename)
现在,出现FileNotFoundError异常时,将执行except代码块中的代码,但什么都不会发生。这种错误发生时,不会出现traceback,也没有任何输出。用户将看到存在的每个文件包含多少个单词,但没有任何迹象表明有一个文件未找到
pass语句还充当了占位符,它提醒你在程序的某个地方什么都没有做,并且以后也许要在这里做些什么。例如,在这个程序中,我们可能决定将找不到的文件的名称写入到文件missing_files.txt中。用户看不到这个文件,但我们可以读取这个文件,进而处理所有文件找不到的问题。
决定报告哪些错误
在什么情况下该向用户报告错误?在什么情况下又应该在失败时一声不吭呢?如果用户知道要分析哪些文件,他们可能希望在有文件没有分析时出现一条消息,将其中的原因告诉他们。如果用户只想看到结果,而并不知道要分析哪些文件,可能就无需在有些文件不存在时告知他们。向用户显示他不想看到的信息可能会降低程序的可用性。Python的错误处理结构让你能够细致地控制与用户分享错误信息的程度,要分享多少信息由你决定。
编写得很好且经过详尽测试的代码不容易出现内部错误,如语法或逻辑错误,但只要程序依赖于外部因素,如用户输入、存在指定的文件、有网络链接,就有可能出现异常。凭借经验可判断该在程序的什么地方包含异常处理块,以及出现错误时该向用户提供多少相关的信息。
你可以使用方法count()来确定特定的单词或短语在字符串中出现了多少次。
>>> line="Row,row,row your boat"
>>> line.count('row')
2
>>> line.lower().count('row')
3
存储数据
很多程序都要求用户输入某种信息,如让用户存储游戏首选项或提供要可视化的数据。
不管专注的是什么,程序都把用户提供的信息存储在列表和字典等数据结构中。
用户关闭程序时,你几乎总是要保存他们提供的信息;
一种简单的方式是使用模块json来存储数据。
模块json让你能够将简单的Python数据结构转储到文件中,并在程序再次运行时加载该文件中的数据.
你还可以使用json在Python程序之间分享数据。
更重要的是,JSON数据格式并非Python专用的,这让你能够将以JSON格式存储的数据与使用其他编程语言的人分享。这是一种轻便格式,很有用,也易于学习。(嗯嗯,这很值得学习啊)
JSON(JavaScript ObjectNotation)格式最初是为JavaScript开发的,但随后成了一种常见格式,被包括Python在内的众多语言采用。
使用json.dump()和json.load()
我们来编写一个存储一组数字的简短程序,再编写一个将这些数字读取到内存中的程序。第一个程序将使用json.dump()来存储这组数字,而第二个程序将使用json.load()。
- 函数json.dump()接受两个实参:要存储的数据以及可用于存储数据的文件对象。
import json
numbers=[2,3,5,7,11,13]
filename='numbers.json'
with open(filename,'w') as f_obj:
json.dump(numbers,f_obj)
我们先导入模块json,再创建一个数字列表。
通常使用文件扩展名.json来指出文件存储的数据为JSON格式。
接下来,我们以写入模式打开这个文件,让json能够将数据写入其中。
我们使用函数json.dump()将数字列表存储到文件numbers.json中。
下面再编写一个程序,使用json.load()将这个列表读取到内存中:
import json
filename='numbers.json'
with open(filename) as f_obj:
numbers=json.load(f_obj)
print(numbers)
这次我们以读取方式打开这个文件
我们使用函数json.load()加载存储在numbers.json中的信息,并将其存储到变量numbers中。
最后,我们打印恢复的数字列表
【这是一种在程序之间共享数据的简单方式。】
保存和读取用户生成的数据
对于用户生成的数据,使用json保存它们
import json
username=input("What is your name?")
filename='username.json'
with open(filename,'w') as f_obj:
json.dump(username,f_obj)
print("We will rember you when you come back, "+username)
现在再编写一个程序,向其名字被存储的用户发出问候
import json
filename='username.json'
with open(filename) as f_obj:
username=json.load(f_obj)
print("Welcome back, "+username)
我们需要将这两个程序合并到一个程序(remember_me.py)中。这个程序运行时,我们将尝试从文件username.json中获取用户名,因此我们首先编写一个尝试恢复用户名的try代码块。如果这个文件不存在,我们就在except代码块中提示用户输入用户名,并将其存储在username.json中,以便程序再次运行时能够获取它:
remember_me.py
import json
#如果以前存储了用户名,就加载它
#否则,就提示用户输入用户名并存储它
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 will remember you when you come back,"+username)
else:
print("Welcome back,"+username+"!")
重构
你经常会遇到这样的情况:代码能够正确地运行,但可做进一步的改进——将代码划分为一系列完成具体工作的函数。这样的过程被称为重构。重构让代码更清晰、更易于理解、更容易扩展。
要重构remember_me.py,可将其大部分逻辑放到一个或多个函数中。remember_me.py的重点是问候用户,因此我们将其所有代码都放到一个名为greet_user()的函数中
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 will remember you when you come back,"+username)
else:
print("Welcome back,"+username+"!")
greet_user()
函数greet_user()所做的不仅仅是问候用户,还在存储了用户名时获取它,而在没有存储用户名时提示用户输入一个。下面来重构greet_user(),让它不执行这么多任务。为此,我们首先将获取存储的用户名的代码移到另一个函数中:
import json
def get_stored_username():
"""如果存储了用户名,就获取它"""
filename='username.json'
try:
with open(filename) as f_obj:
username=json.load(f_obj)
except FileNotFoundError :
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 will remember you when you come back,"+username)
else:
print("Welcome back,"+username+"!")
greet_user()
我们还需将greet_user()中的另一个代码块提取出来:将没有存储用户名时提示用户输入的代码放在一个独立的函数中
import json
def get_stored_username():
"""如果存储了用户名,就获取它"""
filename='username.json'
try:
with open(filename) as f_obj:
username=json.load(f_obj)
except FileNotFoundError :
return username
def get_new_username():
"""提示用户输入用户名"""
username = input("What is 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 will remember you when you come back,"+username)
greet_user()
书中的小结
- 如何使用文件;
- 如何一次性读取整个文件,
- 以及如何以每次一行的方式读取文件的内容;
- 如何写入文件,以及如何将文本附加到文件末尾;
- 什么是异常以及如何处理程序可能引发的异常;
- 如何存储Python数据结构,以保存用户提供的信息,避免用户每次运行程序时都需要重新提供。