目录

一、从文件中读取数据:

1.读取整个文件

2.文件路径

3.逐行读取

4.创建一个包含文件各行内容的列表

5.使用文件的内容

6.包含一百万位的大型文件

7.测试圆周率是否包含你的生日

二、写入文件:

1.写入空文件

2.写入多行

3.附加到文件

三、异常:

1.处理ZeroDivisionError异常

2.使用try-expect代码块

3.使用异常避免崩溃

4.else代码块

5.FileNotFoundError异常:

6.分析文本

7.使用多个文件

8.失败时一声不吭

9.决定报告哪些错误


一、从文件中读取数据

文本文件可存储的数据量多得难以置信:天气数据、交通数据、社会经济数据、文学作品等。每当需要分析或修改,读取文件都很有用,对数据分析应用程序来说尤其如此。例如,你可以编写这样的一个程序:读取一个文本文件的内容,重新设置这些数据的格式并将其写入文件,让浏览器能够显示这些内容。

要使用文本文件中的信息,首先需要将信息读取到内存中。为此,你可以一次性读取文件的全部内容,也可以以每次一行的方式逐行读取。

1.读取整个文件

要读取文件,需要一个包含几行文本的文件。下面首先来创建一个文件,它包含精确到小数点后30位的圆周率值,且在小数点后每10位处都换行:

pi_digits.txt

3.1415926535

   8979323846

   2643383279
 

要手动尝试后续示例,可在编辑器中输入这些数据行。再将文件保存为pi_digits.txt。然后,将该文件保存到本程序员所在的目录中。下面的程序打开并读取这个文件,再将其内容显示到屏幕上:

file_reader.py

with open('pi_digits.txt') as file_object:
    content = file_object.read()
    print(content)


Output:
------------------
3.1415926535

   8979323846

   2643383279


------------------

在这个程序中,第1行代码做了大量的工作。我们首先来看函数open()。要以任何方式使用文件------哪怕仅仅是打印其内容,都得先打开文件。在这个示例中,当前运行的是file_reader.py,因此python在file_reader.py所在的目录中查找pi_digits.txt。函数open()返回一个表示文件的对象。在这里,open('pi_digits.txt')返回一个表示文件pi_digits.txt的对象;python将这个对象存储在后面使用的变量中。

关键字with在不需要访问文件后将其关闭。在这个程序中注意到调用了open(),但没有调用close();你也可以调用open()和close()来打开和关闭文件,但这样做时,如果程序存在dug,导致close()语句未执行,文件将不会关闭。这看似微不足道,但未妥善关闭文件可能会导致数据丢失或受损。如果在程序中过早地调用close(),你会发现需要使用文件时它已关闭(无法访问),这会导致更多的错误。并非在任何情况下都能轻松确定关闭文件的恰当时机,但通过使用前面所示的结构,可让python去确定;你只管打开文件,并在需要时使用它,python自会在合适的时候自动将其关闭。

有了表示pi_digits.txt的文件对象后,使用read()读取这个文件的全部内容,并将其作为一个长长的字符串存储在contents中。这样,通过打印contents的值,就可以将这个文本文件的全部内容显示出来。

因为read()到达文件末尾时返回一个空字符串,而将这个空字符串显示出来就是一个空行。要删除空行的末尾,可在print语句中使用rstrip():

with open('pi_digits.txt') as file_object:
    contents = file_object.read()
    print(contents.rstrip())

Output:
-----------------------
3.1415926535

   8979323846

   2643383279
-----------------------

python通过rstrip()删除(剥除)字符串末尾的空白。现在,输出与原始文件的内容完全相同。

2.文件路径

当你将类似pi_digits.txt这样的简单文件名传递给函数open()时,python将在当前执行的文件(.py程序文件)所在的目录中查找文件。

根据你组织文件的方式,有时可能要打开不在程序文件所属目录中的文件。例如,你可能将程序文件存储在了文件夹python_work中,而在文件夹python_work中,有一个名为text_files的文件夹,用于存储程序文件操作的文本文件。虽然文件夹text_files包含在文件夹python_work中,但仅向open()传递位于该文件夹中的文件的名称也不可行,因为python只在文件夹python_work中查找,而不会在其子文件夹text_files中查找。要让python打开不与程序文件位于同一个目录中的文件,需要提供文件路径,它让python到系统的特定位置去查找。

由于文件夹text_files位于文件夹python_work中,因此可使用相对文件路径来打开该文件夹中的文件。相对文件路径让python到指定的位置去查找,而该位置是相对于当前运行的程序所在目录的。在Linux和OS X中,你可以这样编写代码:

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

这行代码让python到文件夹python_work下的文件夹text_files中去查找指定的.txt文件。在Windows系统中,在文件路径中使用反斜杠(\)而不是斜杠(/):

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

你还可以将文件在计算机中的准确位置告诉python,这样就不用关心当前运行的程序存储在什么地方了。这称为绝对文件路径。在相对文件路径行不通时,可使用绝对路径。例如,如果text_files并不在文件夹python_work中,而在文件夹other_files中,则向open()传递路径'text_files/filename.txt'行不通,因为python只在文件夹python_work中查找该位置。为明确地指出你希望python到哪里去查找,你需要提供完整的路劲。

绝对路径通常比相对路径更长,因此将其存储在一个变量中,再将该变量传递给open()会有所帮助。在Linux和OS X中,绝对路径类似于下面这样:

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

而在Windows系统中,它们类似于下面这样:

file_name = 'C:\Users\ehmattes\other_files\text_files\filename.txt'
with open(file_path) as file_object:

通过使用绝地路径,可读取系统任何地方的文件。就目前而言,最简单的做法是,要么将数据存储在程序文件所在的目录,要么将其存储在程序文件所在目录下的一个文件夹(如text_file)中。

Windows系统有时能够正确地解读文件路径中的斜杠。如果你使用的是Windows系统,且结果不符合预期,请确保在文件路径中使用的是反斜杠。另外,由于反斜杠在python中被视为转义标记,为在WIndows中确保万无一失,应以原始字符串的方式指定路径,即在开头的单引号前加上r。

3.逐行读取

读取文件时,常常需要检查其中的每一行:你可能要在文件中查找特定的信息,或者要以某种方式修改文件中的文本。例如,你可能要遍历一个天气数据的文件,并使用天气描述中包含字样sunny的行。在新闻报道中,你可能会查找包含标签<headlines>的行,并按特定的格式设置它。要以每次一行的方式检查文件,可对文件对象使用for循环:

file_reader.py

filename = 'pi_digits.txt'

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


Output:
-------------------------
3.1415926535



   8979323846



   2643383279

-------------------------

将要读取的文件名存储在变量filename中,这是使用文件时一种常见的做法。由于变量filename表示的并非实际文件------它只是一个让python知道哪里去查找文件的字符串,因此可以轻松地将'pi_digits.txt'替换为你要使用的另一个文件的名称。调用oepn()后,将一个表示文件及其内容的对象存储到了变量file_object中。这里也使用了关键字with,让pyhon负责妥善地打开和关闭文件。为查看文件的内容,通过对文件对象执行循环来遍历文件中的每一行。打印每一行时,发现空白行更多了。为何会出现这些空白行呢?因为在这个文件中,每行的末尾都有一个看不见的换行符,而print语句也会加上一个换行符,因此每行末尾都有两个换行符:一个来自文件,另一个来自print语句。要消除这些多余的空白行,可在print语句使用rstrip()。

filename = 'pi_digits.txt'

with open(filename) as file_object:
    for line in file_object:
       print(line.rstrip())


Output:
----------------------
3.1415926535

   8979323846

   2643383279
----------------------

现在,输出又与文件内容完全相同了。

4.创建一个包含文件各行内容的列表

使用关键字with时,open()返回的文件对象只是在with代码块内可用。如果要在with代码块外访问文件的内容,可在with代码内将文件的各行存储在一个列表中,并在with代码块外使用该列表:你可以立即处理文件的各个部分,也可推迟程序后再处理。

下面的实例在with代码块中将文件pi_digits.txt各行存储在一个列表中,再在with代码块外打印它们:

filename = 'pi_digits.txt'

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

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


Output:
---------------
3.1415926535

   8979323846

   2643383279
---------------

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

5.使用文件的内容

将文件读取到内存后,就可以以任何方式使用这些数据了。下面以简单的方式使用圆周率的值。首先创建一个字符串,包含文件中存储的所有数字,且没有任何空格:

pi_string.py

filename = 'pi_digits.txt'

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

pi_string = ''

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

print(pi_string)
print(len(pi_string))


Output:
-----------------------------------------
3.1415926535   8979323846   2643383279
38
-----------------------------------------

就像前一个示例一样,首先打开文件,并将其中的所有行都存储在一个列表中,首先创建了一个变量------pi_string,用于存储圆周率。接下来,使用一个循环将各行都加入pi_string,并删除每行末尾的换行符。接下来,打印这个字符串及其长度,在变量pi_string存储的字符串,包含原来位于每行坐标的空格,为删除这些空格,可使用strip()而不是rstrip():

filename = 'pi_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))


Output:
-----------------------------------
3.141592653589793238462643383279
32
-----------------------------------

这样,就得到了一个这样的字符串:它包含精确到30位小数的圆周率值。这个字符串长32字符,因为它还包含整数部分的3和小数点。读取文本文件时,python将其中的所有文件都解读为字符串。如果你读取的是数字,并要将其作为数值使用,就必须使用函数int()将其转换成为整数,或使用float()将其转换为浮点数。

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))


Output:
-------------------------------------------------------------
3.141592653589793238462643383279502884197169939937510...

1000002
-------------------------------------------------------------

输出表明,创建的字符串确实包含精确到小数点后1 000 000位的圆周率值。

7.测试圆周率是否包含你的生日

我一直想知道自己的生日是否包含在圆周率中。下面来扩展刚才编写的程序,以确定某个人的生日是否包含在圆周率的前1 000 000位中。为此,可将生日表示为一个由数字组成的字符串,再检查这个字符串是否包含在pi_string中:

filename = 'pi_million_digits.txt'

with open(filename) as file_object:
    line = file_object.readlines()
    pi_string = ''
    for line in lines:
        pi_string += line.strip()

birthday = input("Enter your birthyday, in the form mmdddyy: ")
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.")

Output:
----------------------------------------------------------
Enter your birthday, in the from mmddyy: 120372
Your birthday appears in the first million digits of pi!
----------------------------------------------------------

提示用户输入其生日,在接下来,检查这个字符串是否包含在pi_string中。运行一下这个程序,生日确实出现在了圆周率值中!读取文件的内容后,就可以以你能想到的任何方式对其进行分析。

二、写入文件

保存数据的最简单的方式之一是将其写入到文件中。通过将输出写入文件,即便关闭包含程序输出的终端窗口,这些输出也依然存在:你可以在程序结束后查看这些输出,可与别人分享输出文件,还可编写程序来将这些输出读取到内存中并进行处理。

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()将其转换为字符串格式。python只能将字符串写入文本文件。要将数值数据存储到文本文件中,必须先使用函数str将其转换成字符串格式。

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.

 像显示到终端输出一样,还可以使用使用空格、制表符合空行来设置这些输出的格式。

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 dataset.\n")
     file_object.write("I love creating apps that can run in a brower.\n")

 开始打开文件时指定了实参'a',以便将内容附加到文件末尾,而不是覆盖文件原来的内容。接下来,又写入了两行,它们被添加到文件programming.txt末尾:

programming.txt

I love programming.
I love creating new games.
I also love finding meaning in large dataset.
I love creating apps that can run in a brower.

 最终的结果是,文件原来的内容孩子,它们后面使我们刚添加的内容。

三、异常:

python使用被称为异常的特殊对象来管理程序执行期间发生的错误。每当发生让python不知道所措的错误时,它都会创建一个异常对象。如果你编写了处理该异常对象的代码,程序将继续运行;如果你未对异常进行处理,程序将停止,并显示了一个traceback,其中包含有关异常的报告。

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

1.处理ZeroDivisionError异常

输入代码:

print(5/0)

Output:
--------------------------------------------------------------
Traceback (most recent call last):
  File "D:/pylearning/error/error.py", line 1, in <module>
    print(5/0)
ZeroDivisionError: division by zero
--------------------------------------------------------------

显然python无法这么做,因此你将看到一个traceback,在上述traceback中,zeroDivisionError是一个异常对象。python无法按你的要求做时,就会创建这种对象。在这种情况下,python将停止运行程序,并指出引发了哪种异常,而我们可根据这些信息对程序进行修改。

2.使用try-expect代码块

当你认为可能发生了错误时,可编写一个try-expect代码来处理可能引发的异常。让你python尝试运行一些代码,并告诉它如果这些代码引发了指定的异常,该怎么办。

处理zeroDivisionError异常时try-expect代码块类似于下面这样:

try:
  print(5/0)
except ZeroDivisionError:
    print("You can't divide by zero!")

Output:
---------------------------
You can't divide by zero!
---------------------------

将导致错误的代码行print(5/0)放在了一个try模块中。如果try代码中的代码运行起来没有问题,python将跳过expect代码块;如果try代码块中的代码导致了错误,python将查找这样的expect代码,并运行其中的代码,即其中指定的错误与引发的错误相同。在这个示例中,try代码块中的代码引发了zeroDivisonError异常,因此python指出了该如何解决问题的expect代码块,并运行其中的代码。这样,用户看到的是一条友好的错误信息,而不是traceback。如果try-expect代码块后面还有其他代码,程序将接着运行,因为已经告诉了python如何处理这种错误。

3.使用异常避免崩溃

发生错误时,如果程序还有工作没有完成,妥善处理错误就尤其重要。这种情况经常出现在要求用户提供输入的程序中;如果程序能够妥善地处理无效输入,就能再提示用户提供有效输入而不至于崩溃。

division.py

print("Give me two numbers, and I'll divide them.")
print("Enter 'q' to quit.")

while True :
    first_number = input("\n First number: ")
    if first_number == 'q':
        break
    second_number = input("Second number: ")
    if first_number == 'q':
        break
    answer = int(first_number) / int(second_number)
    print(answer)


Output:
---------------------------------------------------------------
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 "D:/pylearning/error/error.py", line 11, in <module>
    answer = int(first_number) / int(second_number)
ZeroDivisionError: division by zero
---------------------------------------------------------------

这个程序提示用户输入一个数字,并将其存储到变量first_number中;如果用户输入的不是表示推出的q,就再次提示用户输入一个数字,并将其存储到变量second_number中。接下来计算这两个数字的商。这个程序没有采取任何处理错误的措施,因此让它执行除数为0的出发运算时,它将崩溃。程序崩溃可不好,但让用户看到traceback也不是好主意。不懂技术的用户会被它们搞糊涂,而且如果用户怀有恶意,他会通过traceback获悉你不希望他知道的信息。例如,他将知道你的程序文件中的名称,还将看到部分不能正确运行的代码。有时候,训练有素的攻击者可根据这些信息判断出可对你的代码发起什么样的攻击。

4.else代码块

通过将可能引发错误的代码块放在try-expect代码块中,可提高这个程序抵御错误的能力。错误是执行处罚运算的代码导致的,因此需要将它放在try-expect代码中。这个示例还包含一个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)


Output:
---------------------------------------------
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
2.5
---------------------------------------------

让python尝试执行try代码块中的除法运算,这个代码块只包含可能导致错误的代码。依赖于try代码块成功执行的代码都是放在else代码块中;在这个示例中,如果出发运算成功,就使用else代码块来打印结果。expect代码块告诉python,出现ZeroDivisionError异常时该怎么办。如果try代码块因除零错误而失败,就打印一条友好的消息,告诉用户如何避免这种错误。程序将继续运行,用户根本看不到traceback。

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

python尝试执行try代码中的代码,只有可能引发异常的代码才放到try语句中。有时候有一些仅在try代码成功执行时才需要运行的代码;这些代码应放在else代码中。expect代码块告诉python,如果它尝试运行try代码块中的代码时引发了指定的异常该怎么办。通过预测可能发生错误的代码,可编写健壮的程序,它们即便面临无效数据或缺少资源,也能继续运行,从而能够抵御无意的用户错误和恶意攻击。

5.FileNotFoundError异常:

使用文件时,一种常见的问题就是找不到文件:

你要查找的文件可能在其他地方,文件名可能不正确或者这个文件根本就不存在。对于所有这些情形,都可以使用try-expect代码块以直观的方式进行处理。

尝试读取一个不存在的文件。下面的程序尝试读取文件alice.txt的内容,但我没有将这个文件存储在alice.py所在的目录中:

alice.py

filename = 'alice.txt'

with open(filename) as f_obj:
    contents = f_obj.read()


Output:
-----------------------------------------------------------------------
Traceback (most recent call last):
  File "D:/pylearning/error/error.py", line 3, in <module>
    with open(filename) as f_obj:
FileNotFoundError: [Errno 2] No such file or directory: 'alice.txt'
-----------------------------------------------------------------------

python无法读取不存在的文件,因此它引发一个异常,在上述traceback中,最后一行报告了FileNotFoundError异常,这是python找不到要打开的文件时创建的异常。在这个示例中,这个错误是函数open()导致的,因此要处理这个错误,必须将try语句放在包含open()的代码行之前:

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)


Output:
------------------------------------------
Sorry, the file alice.txt does not exist.
------------------------------------------

在这个示例中,try代码块引发FileNotFoundError异常,因此python找出与该错误匹配的expect代码块,并运行其中的代码。最终的结果是显示一条友好的错误消息,而不是traceback,如果文件不存在,这个程序说明都不做,因此错误处理代码的意义。下面来扩展这个示例,看看在你使用多个文件时,异常处理可提供什么样的帮助。

6.分析文本

你可以分析包含整本书的文本文件。很多经典文学作品都是以简单文本文件的方式提供的,因为它们不受版权限制。

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

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

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

filename = 'alice.txt'

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

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


Output:
------------------------------------
The file.txt has about 29461 words.
------------------------------------

把文件alice.txt移到了正确的目录中,让try代码块能够成功地执行。开始对变量contents(它现在是一个长长的字符串,包含童话Alice in  Wonderland的全部文本)调用方法split(),以生成一个列表,其中包含这部童话中的所有单词。当我们使用len()来确定这个列表的长度时,就知道了原始字符串大致包含多少个单词。然后,打印一条消息,指出文件包含多少个单词。这些代码都放在else代码中,因为仅当try代码块成功时才执行它们。输出指出了文件alice.txt包含多少个单词,这个数字有点大,因为这里使用的文本文件包含出版商提供的额外信息,但与童话Alice in Wonderland的长度相当一致。

7.使用多个文件

下面多分析几本书。这样做之前,先将这个程序的大部分代码移到一个名为count_words()的函数中,这样对多本书进行分许时将更容易:

word_count.py

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 = "alice.txt"
count_words(filename)

这些代码大都与原来一样,只是将它们移到了函数count_words()中,并增加了缩进量。修改程序的同时更新注释是个不错的习惯,因此我们将注释改成了文档字符串,并稍微调整了一下措辞。

现在可以编写一个简单的循环,计算分析的任何文本包含多少个单词了。为此,将要分析的文件的名称存储在一个列表中,然后对列表中的每个文件都调用count_words()。尝试计算Alice in Wonderland、 Siddharha、Moby Dick和Little Women分别包含多少个单词,它们都不受版权限制。故意没有将siddhartha.txt放到word_count.py所在的目录中,让你能够看到这个程序在文件不存在时处理得有多出色:

def count_words(filename):
    --snip--

filenames = ['alice.txt', 'siddhartha.txt', 'moby_dick.txt', 'little_women.txt']
for filename in filenames:
    count_words(filename)

Output:
---------------------------------------------------
The file alice.txt has about 29461 words.
Sorry, the file siddhartha.txt does not exist.
The file moby_dick.txt has about 215316 words.
The file little_women.txt has about 189079 words.
----------------------------------------------------

文件siddhartha.txt不存在,但这丝毫不影响这个程序处理其他文件。在这个示例中,使用try-expect代码块提供了两个重要的优点,避免让用户看到traceback;让程序能够继续分析能够找到的其他文件。如果不捕获因找不到siddhartha.txt而引发的FileNotFoundError异常,用户将看到完整的traceback,而程序将在尝试分析Siddhartha后停止运行------根本不分析Moby Dick和LIttle Women。

8.失败时一声不吭

在前一个示例中,告诉用户有一个文件找不到。但并非每次捕获到异常时都需要告诉用户,有时候你希望程序在发生异常时一声不吭,就像什么都没有发生一样继续运行。要让程序在失败时一声不吭,可像通常那样编写try代码块,但在expect代码块中明确地告诉pyton什么都不要做。python有一个pass语句,可在代码块中使用它来让python什么都不要做:

def count_words(filename):
    """计算一个文件大致包含多少个单词"""
    try: 
       --snip--
    except FileNotFoundError:
       pass
    else:
       --snip--

filename = ['alice.txt', 'siddhartha.txt', 'moby_dick.txt', 'little_women.txt']
for filename in filenames:
    count_words(filename)


Output:
---------------------------------------------------
The file alice.txt has about 29561 words.
The file moby_dick.txt has about 215316 words.
The file little_women.txt has about 189079 words.
---------------------------------------------------

相比于前一个程序,这个程序唯一不同的地方是pass语句。现在,出现FileNotFoundError异常时,将执行expect代码块中的代码,但什么都不会发生。这种错误发生时,不会出现traceback,也没有任何输出。用户将看到存在的每个文件包含多少个单词,但没有任何迹象表明有一个文件未找到。 pass语句还充当了占位符,它提醒你在程序的某个地方什么都没有做,并且以后也许要在这里做些什么。例如,在这个程序中,可能决定找不到的文件的名称写入到文件missing_files.txt中。用户看不到这个文件,但可以读取这个文件,进而处理所有文件找不到的问题。

9.决定报告哪些错误

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

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