首页 » Python编程快速上手 » Python编程快速上手全文在线阅读

《Python编程快速上手》第8章 读写文件

关灯直达底部

当程序运行时,变量是保存数据的好方法,但如果希望程序结束后数据仍然保持,就需要将数据保存到文件中。你可以认为文件的内容是一个字符串值,大小可能有几个GB。在本章中,你将学习如何使用Python在硬盘上创建、读取和保存文件。

8.1 文件与文件路径

文件有两个关键属性:“文件名”(通常写成一个单词)和“路径”。路径指明了文件在计算机上的位置。例如,我的 Windows 7 笔记本上有一个文件名为projects.docx,它的路径在C:/Users/asweigart/Documents。文件名中,最后一个句点之后的部分称为文件的“扩展名”,它指出了文件的类型。project.docx是一个Word文档,Users、asweigart和Documents都是指“文件夹”(也成为目录)。文件夹可以包含文件和其他文件夹。例如,project.docx在Documents文件夹中,该文件夹又在asweigart文件夹中,asweigart文件夹又在Users文件夹中。图8-1展示了这个文件夹的组织结构。

路径中的C:/部分是“根文件夹”,它包含了所有其他文件夹。在Windows中,根文件夹名为C:/,也称为C:盘。在OS X和Linux中,根文件夹是/。在本书中,我使用Windows风格的根文件夹,C:/。如果你在OS X或Linux上输入交互式环境的例子,请用/代替。

图8-1 在文件夹层次结构中的一个文件

附加卷,诸如DVD驱动器或USB闪存驱动器,在不同的操作系统上显示也不同。在Windows上,它们表示为新的、带字符的根驱动器。诸如D:/或E:/。在OS X上,它们表示为新的文件夹,在/Volumes文件夹下。在Linux上,它们表示为新的文件夹,在/mnt("mount")文件夹下。同时也要注意,虽然文件夹名称和文件名在Windows和OS X上是不区分大小写的,但在Linux上是区分大小写的。

8.1.1 Windows上的倒斜杠以及OS X和Linux上的正斜杠

在Windows上,路径书写使用倒斜杠作为文件夹之间的分隔符。但在OS X和Linux上,使用正斜杠作为它们的路径分隔符。如果想要程序运行在所有操作系统上,在编写Python脚本时,就必须处理这两种情况。

好在,用os.path.join函数来做这件事很简单。如果将单个文件和路径上的文件夹名称的字符串传递给它,os.path.join就会返回一个文件路径的字符串,包含正确的路径分隔符。在交互式环境中输入以下代码:

>>> import os>>> os.path.join('usr', 'bin', 'spam')'usr//bin//spam'  

我在Windows上运行这些交互式环境的例子,所以,os.path .join('usr', 'bin', 'spam')返回'usr/bin/spam'(请注意,倒斜杠有两个,因为每个倒斜杠需要由另一个倒斜杠字符来转义)。如果我在OS X或Linux上调用这个函数,该字符串就会是'usr/bin/spam'。

如果需要创建文件名称的字符串,os.path.join函数就很有用。这些字符串将传递给几个文件相关的函数,本章将进行介绍。例如,下面的例子将一个文件名列表中的名称,添加到文件夹名称的末尾。

>>> myFiles = ['accounts.txt', 'details.csv', 'invite.docx']>>> for filename in myFiles:print(os.path.join('C://Users//asweigart', filename))C:/Users/asweigart/accounts.txtC:/Users/asweigart/details.csvC:/Users/asweigart/invite.docx  

8.1.2 当前工作目录

每个运行在计算机上的程序,都有一个“当前工作目录”,或cwd。所有没有从根文件夹开始的文件名或路径,都假定在当前工作目录下。利用os.getcwd函数,可以取得当前工作路径的字符串,并可以利用os.chdir改变它。在交互式环境中输入以下代码:

>>> import os>>> os.getcwd'C://Python34'>>> os.chdir('C://Windows//System32')>>> os.getcwd'C://Windows//System32'  

这里,当前工作目录设置为C:/Python34,所以文件名project.docx指向C:/Python34/project.docx。如果我们将当前工作目录改为C:/Windows,文件就被解释为C:/Windows/project.docx。

如果要更改的当前工作目录不存在,Python就会显示一个错误。

>>> os.chdir('C://ThisFolderDoesNotExist')Traceback (most recent call last):  File "< pyshell#18>", line 1, in < module>    os.chdir('C://ThisFolderDoesNotExist')FileNotFoundError: [WinError 2] The system cannot find the file specified:'C://ThisFolderDoesNotExist'  

注意

虽然文件夹是目录的更新的名称,但请注意,当前工作目录(或当前目录)是标准术语,没有当前工作文件夹这种说法。

8.1.3 绝对路径与相对路径

有两种方法指定一个文件路径。

  • “绝对路径”,总是从根文件夹开始。
  • “相对路径”,它相对于程序的当前工作目录。

还有点(.)和点点(..)文件夹。它们不是真正的文件夹,而是可以在路径中使用的特殊名称。单个的句点(“点”)用作文件夹目名称时,是“这个目录”的缩写。两个句点(“点点”)意思是父文件夹。

图8-2是一些文件夹和文件的例子。如果当前工作目录设置为C:/bacon,这些文件夹和文件的相对目录,就设置为图8-2所示的样子。

图8-2 在工作目录C:/bacon中的文件夹和文件的相对路径

相对路径开始处的./是可选的。例如,./spam.txt和spam.txt指的是同一个文件。

8.1.4 用os.makedirs创建新文件夹

程序可以用os.makedirs函数创建新文件夹(目录)。在交互式环境中输入以下代码:

>>> import os>>> os.makedirs('C://delicious//walnut//waffles')  

这不仅将创建C:/delicious文件夹,也会在C:/delicious下创建walnut文件夹,并在C:/delicious/walnut中创建waffles文件夹。也就是说,os.makedirs将创建所有必要的中间文件夹,目的是确保完整路径名存在。图 8-3 展示了这个文件夹的层次结构。

图8-3 os.makedirs('C:/delicious/walnut/waffles')的结果

8.1.5 os.path模块

os.path模块包含了许多与文件名和文件路径相关的有用函数。例如,你已经使用了os.path.join来构建所有操作系统上都有效的路径。因为os.path是os模块中的模块,所以只要执行import os就可以导入它。如果你的程序需要处理文件、文件夹或文件路径,就可以参考本节中这些简短的例子。os.path模块的完整文档在Python网站上:http://docs.python.org/3/library/os.path.html。

注意

本章后面的大多数例子都需要os模块,所以要记得在每个脚本开始处导入它,或在重新启动IDLE时导入它。否则,就会遇到错误消息NameError: name 'os' is not defined。

8.1.6 处理绝对路径和相对路径

os.path模块提供了一些函数,返回一个相对路径的绝对路径,以及检查给定的路径是否为绝对路径。

  • 调用os.path.abspath(path)将返回参数的绝对路径的字符串。这是将相对路径转换为绝对路径的简便方法。
  • 调用os.path.isabs(path),如果参数是一个绝对路径,就返回True,如果参数是一个相对路径,就返回False。
  • 调用os.path.relpath(path, start)将返回从start路径到path的相对路径的字符串。如果没有提供start,就使用当前工作目录作为开始路径。

在交互式环境中尝试以下函数:

>>> os.path.abspath('.')'C://Python34'>>> os.path.abspath('.//Scripts')'C://Python34//Scripts'>>> os.path.isabs('.')False>>> os.path.isabs(os.path.abspath('.'))True  

因为在os.path.abspath调用时,当前目录是C:/Python34,所以“点”文件夹指的是绝对路径'C:/Python34'。

注意

因为在你的系统上,文件和文件夹可能与我的不同,所以你不能完全遵照本章中的每一个例子。但还是请尝试用你的计算机上存在的文件夹来完成例子。

在交互式环境中,输入以下对os.path.relpath的调用:

>>> os.path.relpath('C://Windows', 'C://')'Windows'>>> os.path.relpath('C://Windows', 'C://spam//eggs')'..//..//Windows'>>> os.getcwd'C://Python34'  

调用os.path.dirname(path)将返回一个字符串,它包含path参数中最后一个斜杠之前的所有内容。调用os.path.basename(path)将返回一个字符串,它包含path 参数中最后一个斜杠之后的所有内容。一个路径的目录名称和基本名称如图8-4所示。

图8-4 基本名称跟在路径中最后一个斜杠后,它和文件名一样,
目录名称是最后一个斜杠之前的所有内容

例如,在交互式环境中输入以下代码:

>>> path = 'C://Windows//System32//calc.exe'>>> os.path.basename(path)'calc.exe'>>> os.path.dirname(path)'C://Windows//System32'  

如果同时需要一个路径的目录名称和基本名称,就可以调用os.path.split,获得这两个字符串的元组,像这样:

>>> calcFilePath = 'C://Windows//System32//calc.exe'>>> os.path.split(calcFilePath)('C://Windows//System32', 'calc.exe')  

请注意,可以调用os.path.dirname和os.path.basename,将它们的返回值放在一个元组中,从而得到同样的元组。

>>> (os.path.dirname(calcFilePath), os.path.basename(calcFilePath))('C://Windows//System32', 'calc.exe')  

但如果需要两个值,os.path.split是很好的快捷方式。

同时也请注意,os.path.split不会接受一个文件路径并返回每个文件夹的字符串的列表。如果需要这样,请使用split字符串方法,并根据os.path.sep中的字符串进行分割。回忆一下,根据程序运行的计算机,os.path.sep变量设置为正确的文件夹分割斜杠。

例如,在交互式环境中输入以下代码:

>>> calcFilePath.split(os.path.sep)['C:', 'Windows', 'System32', 'calc.exe']  

在OS X和Linux系统上,返回的列表头上有一个空字符串:

>>> '/usr/bin'.split(os.path.sep)['', 'usr', 'bin']  

split字符串方法将返回一个列表,包含该路径的所有部分。如果向它传递os.path.sep,就能在所有操作系统上工作。

8.1.7 查看文件大小和文件夹内容

一旦有办法处理文件路径,就可以开始搜集特定文件和文件夹的信息。os.path模块提供了一些函数,用于查看文件的字节数以及给定文件夹中的文件和子文件夹。

  • 调用os.path.getsize(path)将返回path参数中文件的字节数。
  • 调用os.listdir(path)将返回文件名字符串的列表,包含path参数中的每个文件(请注意,这个函数在os模块中,而不是os.path)。

下面是我在交互式环境中尝试这些函数的结果:

>>> os.path.getsize('C://Windows//System32//calc.exe')776192>>> os.listdir('C://Windows//System32')['0409', '12520437.cpx', '12520850.cpx', '5U877.ax', 'aaclient.dll',--_snip_--'xwtpdui.dll', 'xwtpw32.dll', 'zh-CN', 'zh-HK', 'zh-TW', 'zipfldr.dll']  

可以看到,我的计算机上的calc.exe程序是776192字节。在我的C:/Windows/ system32下有许多文件。如果想知道这个目录下所有文件的总字节数,就可以同时使用os.path.getsize和os.listdir。

>>> totalSize = 0>>> for filename in os.listdir('C://Windows//System32'):     totalSize = totalSize + os.path.getsize(os.path.join('C://Windows//System32', filename)) >>> print(totalSize)1117846456  

当循环遍历C:/Windows/System32文件夹中的每个文件时,totalSize变量依次增加每个文件的字节数。请注意,我在调用os.path.getsize时,使用了os.path.join来连接文件夹名称和当前的文件名。os.path.getsize返回的整数添加到totalSize中。在循环遍历所有文件后,我打印出totalSize,看看C:/Windows/System32文件夹的总字节数。

8.1.8 检查路径有效性

如果你提供的路径不存在,许多Python函数就会崩溃并报错。os.path模块提供了一些函数,用于检测给定的路径是否存在,以及它是文件还是文件夹。

  • 如果path参数所指的文件或文件夹存在,调用os.path.exists(path)将返回True,否则返回False。
  • 如果path参数存在,并且是一个文件,调用os.path.isfile(path)将返回True,否则返回False。
  • 如果path参数存在,并且是一个文件夹,调用os.path.isdir(path)将返回True,否则返回False。

下面是我在交互式环境中尝试这些函数的结果:

>>> os.path.exists('C://Windows')True>>> os.path.exists('C://some_made_up_folder')False>>> os.path.isdir('C://Windows//System32')True>>> os.path.isfile('C://Windows//System32')False>>> os.path.isdir('C://Windows//System32//calc.exe')False>>> os.path.isfile('C://Windows//System32//calc.exe')True  

利用os.path.exists函数,可以确定DVD或闪存盘当前是否连在计算机上。例如,如果在Windows计算机上,我想用卷名D:/检查一个闪存盘,可以这样做:

>>> os.path.exists('D://')False  

不好!看起来我忘记插入闪存盘了。

8.2 文件读写过程

在熟悉了处理文件夹和相对路径后,你就可以指定文件的位置,进行读写。接下来几节介绍的函数适用于纯文本文件。“纯文本文件”只包含基本文本字符,不包含字体、大小和颜色信息。带有.txt扩展名的文本文件,以及带有.py扩展名的Python脚本文件,都是纯文本文件的例子。它们可以被Windows的Notepad或OS X的TextEdit应用打开。你的程序可以轻易地读取纯文本文件的内容,将它们作为普通的字符串值。

“二进制文件”是所有其他文件类型,诸如字处理文档、PDF、图像、电子表格和可执行程序。如果用Notepad或TextEdit打开一个二进制文件,它看起来就像乱码,如图8-5所示。

图8-5 在Notepad中打开Windows的calc.exe程序

既然每种不同类型的二进制文件,都必须用它自己的方式来处理,本书就不会探讨直接读写二进制文件。好在,许多模块让二进制文件的处理变得更容易。在本章稍后,你将探索其中一个模块:shelve。

在Python中,读写文件有3个步骤:

1.调用open函数,返回一个File对象。

2.调用File对象的read或write方法。

3.调用File对象的close方法,关闭该文件。

8.2.1 用open函数打开文件

要用open函数打开一个文件,就要向它传递一个字符串路径,表明希望打开的文件。这既可以是绝对路径,也可以是相对路径。open函数返回一个File对象。

尝试一下,先用Notepad或TextEdit创建一个文本文件,名为hello.txt。输入Hello world!作为该文本文件的内容,将它保存在你的用户文件夹中。然后,如果使用Windows,在交互式环境中输入以下代码:

>>> helloFile = open('C://Users//_your_home_folder_//hello.txt')  

如果使用OS X,在交互式环境中输入以下代码:

>>> helloFile = open('/Users/_your_home_folder_/hello.txt')  

请确保用你自己的计算机用户名取代your_home_folder。例如,我的用户名是asweigart,所以我在windows下输入'C:/Users/asweigart/hello.txt'。

这些命令都将以读取纯文本文件的模式打开文件,或简称为“读模式”。当文件以读模式打开时,Python只让你从文件中读取数据,你不能以任何方式写入或修改它。在Python中打开文件时,读模式是默认的模式。但如果你不希望依赖于Python的默认值,也可以明确指明该模式,向open传入字符串'r',作为第二个参数。所以open('/Users/asweigart/hello.txt', 'r')和open('/Users/asweigart/hello.txt')做的事情一样。

调用open将返回一个File对象。File对象代表计算机中的一个文件,它只是Python中另一种类型的值,就像你已熟悉的列表和字典。在前面的例子中,你将File对象保存在helloFile变量中。现在,当你需要读取或写入该文件,就可以调用helloFile变量中的File对象的方法。

8.2.2 读取文件内容

既然有了一个File对象,就可以开始从它读取内容。如果你希望将整个文件的内容读取为一个字符串值,就使用File对象的read方法。让我们继续使用保存在helloFile中的hello.txt File对象。在交互式环境中输入以下代码:

>>> helloContent = helloFile.read>>> helloContent'Hello world!'  

如果你将文件的内容看成是单个大字符串,read方法就返回保存在该文件中的这个字符串。

或者,可以使用readlines方法,从该文件取得一个字符串的列表。列表中的每个字符串就是文本中的每一行。例如,在hello.txt文件相同的目录下,创建一个名为sonnet29.txt的文件,并在其中写入以下文本:

When, in disgrace with fortune and men's eyes,I all alone beweep my outcast state,And trouble deaf heaven with my bootless cries,And look upon myself and curse my fate,  

确保用换行分开这4行。然后在交互式环境中输入以下代码:

>>> sonnetFile = open('sonnet29.txt')>>> sonnetFile.readlines[When, in disgrace with fortune and men's eyes,/n', ' I all alone beweep myoutcast state,/n', And trouble deaf heaven with my bootless cries,/n', Andlook upon myself and curse my fate,']  

请注意,每个字符串值都以一个换行字符/n结束。除了文件的最后一行。与单个大字符串相比,字符串的列表通常更容易处理。

8.2.3 写入文件

Python允许你将内容写入文件,方式与print函数将字符串“写”到屏幕上类似。但是,如果打开文件时用读模式,就不能写入文件。你需要以“写入纯文本模式”或“添加纯文本模式”打开该文件,或简称为“写模式”和“添加模式”。

写模式将覆写原有的文件,从头开始,就像你用一个新值覆写一个变量的值。将'w'作为第二个参数传递给open,以写模式打开该文件。不同的是,添加模式将在已有文件的末尾添加文本。你可以认为这类似向一个变量中的列表添加内容,而不是完全覆写该变量。将'a'作为第二个参数传递给open,以添加模式打开该文件。

如果传递给 open的文件名不存在,写模式和添加模式都会创建一个新的空文件。在读取或写入文件后,调用close方法,然后才能再次打开该文件。

让我们整合这些概念。在交互式环境中输入以下代码:

>>> baconFile = open('bacon.txt', 'w')>>> baconFile.write('Hello world!/n')13>>> baconFile.close>>> baconFile = open('bacon.txt', 'a')>>> baconFile.write('Bacon is not a vegetable.')25>>> baconFile.close>>> baconFile = open('bacon.txt')>>> content = baconFile.read>>> baconFile.close>>> print(content)Hello world!Bacon is not a vegetable.  

首先,我们以写模式打开bacon.txt。因为还没有bacon.txt,Python就创建了一个。在打开的文件上调用write,并向write传入字符串参数'Hello world! /n',将字符串写入文件,并返回写入的字符个数,包括换行符。然后关闭该文件。

为了将文本添加到文件已有的内容,而不是取代我们刚刚写入的字符串,我们就以添加模式打开该文件。向该文件写入'Bacon is not a vegetable.',并关闭它。最后,为了将文件的内容打印到屏幕上,我们以默认的读模式打开该文件,调用read,将得到的内容保存在content中,关闭该文件,并打印content。

请注意,write方法不会像print函数那样,在字符串的末尾自动添加换行字符。必须自己添加该字符。

8.3 用shelve模块保存变量

利用shelve模块,你可以将Python程序中的变量保存到二进制的shelf文件中。这样,程序就可以从硬盘中恢复变量的数据。shelve模块让你在程序中添加“保存”和“打开”功能。例如,如果运行一个程序,并输入了一些配置设置,就可以将这些设置保存到一个shelf文件,然后让程序下一次运行时加载它们。

在交互式环境中输入以下代码:

>>> import shelve>>> shelfFile = shelve.open('mydata')>>> cats = ['Zophie', 'Pooka', 'Simon']>>> shelfFile['cats'] = cats>>> shelfFile.close  

要利用shelve模块读写数据,首先要导入它。调用函数shelve.open并传入一个文件名,然后将返回的值保存在一个变量中。可以对这个变量的shelf值进行修改,就像它是一个字典一样。当你完成时,在这个值上调用close。这里,我们的shelf值保存在shelfFile中。我们创建了一个列表cats,并写下shelfFile['cats'] =cats,将该列表保存在shelfFile中,作为键'cats'关联的值(就像在字典中一样)。然后我们在shelfFile上调用close。

在Windows上运行前面的代码,你会看到在当前工作目录下有3个新文件:mydata.bak、mydata.dat和mydata.dir。在OS X上,只会创建一个mydata.db文件。

这些二进制文件包含了存储在shelf中的数据。这些二进制文件的格式并不重要,你只需要知道shelve模块做了什么,而不必知道它是怎么做的。该模块让你不用操心如何将程序的数据保存到文件中。

你的程序稍后可以使用shelve模块,重新打开这些文件并取出数据。shelf值不必用读模式或写模式打开,因为它们在打开后,既能读又能写。在交互式环境中输入以下代码:

>>> shelfFile = shelve.open('mydata')>>> type(shelfFile)< class 'shelve.DbfilenameShelf'>>>> shelfFile['cats']['Zophie', 'Pooka', 'Simon']>>> shelfFile.close  

这里,我们打开了shelf文件,检查我们的数据是否正确存储。输入shelfFile['cats']将返回我们前面保存的同一个列表,所以我们就知道该列表得到了正确存储,然后我们调用close。

就像字典一样,shelf值有keys和values方法,返回shelf中键和值的类似列表的值。因为这些方法返回类似列表的值,而不是真正的列表,所以应该将它们传递给list函数,取得列表的形式。在交互式环境中输入以下代码:

>>> shelfFile = shelve.open('mydata')>>> list(shelfFile.keys)['cats']>>> list(shelfFile.values)[['Zophie', 'Pooka', 'Simon']]>>> shelfFile.close  

创建文件时,如果你需要在Notepad或TextEdit这样的文本编辑器中读取它们,纯文本就非常有用。但是,如果想要保存Python程序中的数据,那就使用shelve模块。

8.4 用pprint.pformat函数保存变量

回忆一下5.2节“漂亮打印”中,pprint.pprint函数将列表或字典中的内容“漂亮打印”到屏幕,而pprint.pformat函数将返回同样的文本字符串,但不是打印它。这个字符串不仅是易于阅读的格式,同时也是语法上正确的Python代码。假定你有一个字典,保存在一个变量中,你希望保存这个变量和它的内容,以便将来使用。pprint.pformat函数将提供一个字符串,你可以将它写入.py文件。该文件将成为你自己的模块,如果你需要使用存储在其中的变量,就可以导入它。

例如,在交互式环境中输入以下代码:

>>> import pprint>>> cats = [{'name': 'Zophie', 'desc': 'chubby'}, {'name': 'Pooka', 'desc': 'fluffy'}]>>> pprint.pformat(cats)"[{'desc': 'chubby', 'name': 'Zophie'}, {'desc': 'fluffy', 'name': 'Pooka'}]">>> fileObj = open('myCats.py', 'w')>>> fileObj.write('cats = ' + pprint.pformat(cats) + '/n')83>>> fileObj.close  

这里,我们导入了pprint,以便能使用pprint.pformat。我们有一个字典的列表,保存在变量cats中。为了让cats中的列表在关闭交互式环境后仍然可用,我们利用pprint.pformat,将它返回为一个字符串。当我们有了cats中数据的字符串形式,就很容易将该字符串写入一个文件,我们将它命名为myCats.py。

import语句导入的模块本身就是Python脚本。如果来自pprint.pformat的字符串保存为一个.py文件,该文件就是一个可以导入的模块,像其他模块一样。

由于Python脚本本身也是带有.py文件扩展名的文本文件,所以你的Python程序甚至可以生成其他Python程序。然后可以将这些文件导入到脚本中。

>>> import myCats>>> myCats.cats[{'name': 'Zophie', 'desc': 'chubby'}, {'name': 'Pooka', 'desc': 'fluffy'}]>>> myCats.cats[0]{'name': 'Zophie', 'desc': 'chubby'}>>> myCats.cats[0]['name']'Zophie'  

创建一个.py文件(而不是利用shelve模块保存变量)的好处在于,因为它是一个文本文件,所以任何人都可以用一个简单的文本编辑器读取和修改该文件的内容。但是,对于大多数应用,利用shelve模块来保存数据,是将变量保存到文件的最佳方式。只有基本数据类型,诸如整型、浮点型、字符串、列表和字典,可以作为简单文本写入一个文件。例如,File对象就不能够编码为文本。

8.5 项目:生成随机的测验试卷文件

假如你是一位地理老师,班上有35名学生,你希望进行美国各州首府的一个小测验。不妙的是,班里有几个坏蛋,你无法确信学生不会作弊。你希望随机调整问题的次序,这样每份试卷都是独一无二的,这让任何人都不能从其他人那里抄袭答案。当然,手工完成这件事又费时又无聊。好在,你懂一些Python。

下面是程序所做的事:

  • 创建35份不同的测验试卷。
  • 为每份试卷创建50个多重选择题,次序随机。
  • 为每个问题提供一个正确答案和3个随机的错误答案,次序随机。
  • 将测验试卷写到35个文本文件中。
  • 将答案写到35个文本文件中。

这意味着代码需要做下面的事:

  • 将州和它们的首府保存在一个字典中。
  • 针对测验文本文件和答案文本文件,调用open、write和close。
  • 利用random.shuffle随机调整问题和多重选项的次序。

第1步:将测验数据保存在一个字典中

第一步是创建一个脚本框架,并填入测验数据。创建一个名为randomQuiz Generator.py的文件,让它看起来像这样:

 #! python3 # randomQuizGenerator.py - Creates quizzes with questions and answers in # random order, along with the answer key.❶ import random # The quiz data. Keys are states and values are their capitals.❷ capitals = {'Alabama': 'Montgomery', 'Alaska': 'Juneau', 'Arizona': 'Phoenix', 'Arkansas': 'Little Rock', 'California': 'Sacramento', 'Colorado': 'Denver', 'Connecticut': 'Hartford', 'Delaware': 'Dover', 'Florida': 'Tallahassee', 'Georgia': 'Atlanta', 'Hawaii': 'Honolulu', 'Idaho': 'Boise', 'Illinois': 'Springfield', 'Indiana': 'Indianapolis', 'Iowa': 'Des Moines', 'Kansas': 'Topeka', 'Kentucky': 'Frankfort', 'Louisiana': 'Baton Rouge', 'Maine': 'Augusta', 'Maryland': 'Annapolis', 'Massachusetts': 'Boston', 'Michigan': 'Lansing', 'Minnesota': 'Saint Paul', 'Mississippi': 'Jackson', 'Missouri': 'Jefferson City', 'Montana': 'Helena', 'Nebraska': 'Lincoln', 'Nevada': 'Carson City', 'New Hampshire': 'Concord', 'New Jersey': 'Trenton', 'New Mexico': 'Santa Fe', 'New York': 'Albany', 'North Carolina': 'Raleigh', 'North Dakota': 'Bismarck', 'Ohio': 'Columbus', 'Oklahoma': 'Oklahoma City', 'Oregon': 'Salem', 'Pennsylvania': 'Harrisburg', 'Rhode Island': 'Providence', 'South Carolina': 'Columbia', 'South Dakota': 'Pierre', 'Tennessee': 'Nashville', 'Texas': 'Austin', 'Utah': 'Salt Lake City', 'Vermont': 'Montpelier', 'Virginia': 'Richmond', 'Washington': 'Olympia', 'West Virginia': 'Charleston', 'Wisconsin': 'Madison', 'Wyoming': 'Cheyenne'} # Generate 35 quiz files.❸ for quizNum in range(35):     # TODO: Create the quiz and answer key files.     # TODO: Write out the header for the quiz.     # TODO: Shuffle the order of the states.     # TODO: Loop through all 50 states, making a question for each.  

因为这个程序将随机安排问题和答案的次序,所以需要导入random模块❶,以便利用其中的函数。capitals变量❷含一个字典,以美国州名作为键,以州首府作为值。因为你希望创建35份测验试卷,所以实际生成测验试卷和答案文件的代码(暂时用TODO注释标注)会放在一个for循环中,循环35次❸(这个数字可以改变,生成任何数目的测验试卷文件)。

第2步:创建测验文件,并打乱问题的次序

现在是时候填入那些TODO了。

循环中的代码将重复执行35次(每次生成一份测验试卷),所以在循环中,你只需要考虑一份测验试卷。首先你要创建一个实际的测验试卷文件,它需要有唯一的文件名,并且有某种标准的标题部分,留出位置,让学生填写姓名、日期和班级。然后需要得到随机排列的州的列表,稍后将用它来创建测验试卷的问题和答案。

在randomQuizGenerator.py中添加以下代码行:

 #! python3 # randomQuizGenerator.py - Creates quizzes with questions and answers in # random order, along with the answer key. --_snip_-- # Generate 35 quiz files. for quizNum in range(35):     # Create the quiz and answer key files.❶     quizFile = open('capitalsquiz%s.txt' % (quizNum + 1), 'w')❷     answerKeyFile = open('capitalsquiz_answers%s.txt' % (quizNum + 1), 'w')       # Write out the header for the quiz.❸     quizFile.write('Name:/n/nDate:/n/nPeriod:/n/n')     quizFile.write((' ' * 20) + 'State Capitals Quiz (Form %s)' % (quizNum + 1))     quizFile.write('/n/n')       # Shuffle the order of the states.     states = list(capitals.keys)❹     random.shuffle(states)       # TODO: Loop through all 50 states, making a question for each.  

测验试卷的文件名将是capitalsquiz<N>.txt,其中<N>是该测验试卷的唯一编号,来自于quizNum,即for循环的计数器。针对capitalsquiz<N>.txt的答案将保存在一个文本文件中,名为capitalsquiz_answers<N>.txt。每次执行循环,'capitalsquiz%s.txt'和'capitalsquiz_answers%s.txt'中的占位符%s都将被(quizNum + 1)取代,所以第一个测验试卷和答案将是capitalsquiz1.txt和capitalsquiz_answers1.txt。在❶和❷的open函数调用将创建这些文件,以'w'作为第二个参数,以写模式打开它们。

❸处write语句创建了测验标题,让学生填写。最后,利用random.shuffle函数❹,创建了美国州名的随机列表。该函数重新随机排列传递给它的列表中的值。

第3步:创建答案选项

现在需要为每个问题生成答案选项,这将是A到D的多重选择。你需要创建另一个for循环,该循环生成测验试卷的50个问题的内容。然后里面会嵌套第三个for循环,为每个问题生成多重选项。让你的代码看起来像这样:

 #! python3 # randomQuizGenerator.py - Creates quizzes with questions and answers in # random order, along with the answer key. --_snip_--     # Loop through all 50 states, making a question for each.     for questionNum in range(50):   # Get right and wrong answers.❶ correctAnswer = capitals[states[questionNum]]❷ wrongAnswers = list(capitals.values)❸ del wrongAnswers[wrongAnswers.index(correctAnswer)]❹ wrongAnswers = random.sample(wrongAnswers, 3)❺ answerOptions = wrongAnswers + [correctAnswer]❻ random.shuffle(answerOptions)   # TODO: Write the question and answer options to the quiz file.   # TODO: Write the answer key to a file.  

正确的答案很容易得到,它作为一个值保存在capitals字典中❶。这个循环将遍历打乱过的states列表中的州,从states[0]到states[49],在capitals中找到每个州,将该州对应的首府保存在correctAnswer中。

可能的错误答案列表需要一点技巧。你可以从capitals字典中复制所有的值❷,删除正确的答案❸,然后从该列表中选择3个随机的值❹。random.sample函数使得这种选择很容易,它的第一个参数是你希望选择的列表,第二个参数是你希望选择的值的个数。完整的答案选项列表是这3个错误答案与正确答案的组合❺。最后,答案需要随机排列❻,这样正确的答案就不会总是选项D。

第4步:将内容写入测验试卷和答案文件

剩下来就是将问题写入测验试卷文件,将答案写入答案文件。让你的代码看起来像这样:

 #! python3 # randomQuizGenerator.py - Creates quizzes with questions and answers in # random order, along with the answer key. --_snip_--   # Loop through all 50 states, making a question for each.   for questionNum in range(50):     --_snip_--     # Write the question and the answer options to the quiz file.     quizFile.write('%s. What is the capital of %s?/n' % (questionNum + 1, states[questionNum]))❶     for i in range(4):❷ quizFile.write(' %s. %s/n' % ('ABCD'[i], answerOptions[i]))     quizFile.write('/n')      # Write the answer key to a file.❸     answerKeyFile.write('%s. %s/n' % (questionNum + 1, 'ABCD'[ answerOptions.index(correctAnswer)]))     quizFile.close     answerKeyFile.close  

一个遍历整数0到3的for循环,将答案选项写入answerOptions列表❶。❷处的表达式'ABCD'[i]将字符串'ABCD'看成是一个数组,它在循环的每次迭代中,将分别求值为'A'、'B'、'C'和'D'。

在最后一行❸,表达式answerOptions.index(correctAnswer)将在随机排序的答案选项中,找到正确答案的整数下标,并且'ABCD'[answerOptions.index(correctAnswer)]将求值为正确答案的字母,写入到答案文件中。

在运行该程序后,下面就是capitalsquiz1.txt文件看起来的样子。但是,你的问题和答案选项当然与这里显示的可能会不同。这取决于random.shuffle调用的结果:

Name:Date:Period:    State Capitals Quiz (Form 1)1. What is the capital of West Virginia?    A. Hartford    B. Santa Fe    C. Harrisburg    D. Charleston2. What is the capital of Colorado?    A. Raleigh    B. Harrisburg    C. Denver    D. Lincoln--snip--  

对应的capitalsquiz_answers1.txt文本文件看起来像这样:

1. D2. C3. A4. C--snip--  

8.6 项目:多重剪贴板

假定你有一个无聊的任务,要填充一个网页或软件中的许多表格,其中包含一些文本字段。剪贴板让你不必一次又一次输入同样的文本,但剪贴板上一次只有一个内容。如果你有几段不同的文本需要拷贝粘贴,就不得不一次又一次的标记和拷贝几个同样的内容。

可以编写一个Python程序,追踪几段文本。这个“多重剪贴板”将被命名为mcb.pyw(因为“mcb”比输入“multiclipboard”更简单)。.pyw扩展名意味着Python运行该程序时,不会显示终端窗口(详细内容请参考附录B)。

该程序将利用一个关键字保存每段剪贴板文本。例如,当运行py mcb.pyw save spam,剪贴板中当前的内容就用关键字spam保存。通过运行py mcb.pyw spam,这段文本稍后将重新加载到剪贴板中。如果用户忘记了都有哪些关键字,他们可以运行py mcb.pyw list,将所有关键字的列表复制到剪贴板中。

下面是程序要做的事:

  • 针对要检查的关键字,提供命令行参数。
  • 如果参数是save,那么将剪贴板的内容保存到关键字。
  • 如果参数是list,就将所有的关键字拷贝到剪贴板。
  • 否则,就将关键词对应的文本拷贝到剪贴板。

这意味着代码需要做下列事情:

  • 从sys.argv读取命令行参数。
  • 读写剪贴板。
  • 保存并加载shelf文件。

如果你使用Windows,可以创建一个名为mcb.bat的批处理文件,很容易地通过“Run…”窗口运行这个脚本。该批处理文件包含如下内容:

@pyw.exe C:/Python34/mcb.pyw %*  

第1步:注释和shelf设置

我们从一个脚本框架开始,其中包含一些注释和基本设置。让你的代码看起来像这样:

 #! python3 # mcb.pyw - Saves and loads pieces of text to the clipboard.❶ # Usage: py.exe mcb.pyw save <keyword> - Saves clipboard to keyword. #py.exe mcb.pyw <keyword> - Loads keyword to clipboard. #py.exe mcb.pyw list - Loads all keywords to clipboard.❷ import shelve, pyperclip, sys❸ mcbShelf = shelve.open('mcb') # TODO: Save clipboard content. # TODO: List keywords and load content. mcbShelf.close 

将一般用法信息放在文件顶部的注释中,这是常见的做法❶。如果忘了如何运行这个脚本,就可以看看这些注释,帮助回忆起来。然后导入模块❷。拷贝和粘贴需要pyperclip模块,读取命令行参数需要sys模块。shelve模块也需要准备好。当用户希望保存一段剪贴板文本时,你需要将它保存到一个shelf文件中。然后,当用户希望将文本拷贝回剪贴板时,你需要打开shelf文件,将它重新加载到程序中。这个shlef文件命名时带有前缀mcb❸。

第2步:用一个关键字保存剪贴板内容

根据用户希望保存文本到一个关键字,或加载文本到剪贴板,或列出已有的关键字,该程序做的事情不一样。让我们来处理第一种情况。让你的代码看起来像这样:

 #! python3 # mcb.pyw - Saves and loads pieces of text to the clipboard. --snip-- # Save clipboard content.❶ if len(sys.argv) == 3 and sys.argv[1].lower == 'save':❷ mcbShelf[sys.argv[2]] = pyperclip.pasteelif len(sys.argv) == 2:❸     # TODO: List keywords and load content. mcbShelf.close  

如果第一个命令行参数(它总是在sys.argv列表的下标1处)是字符串'save' ❶,第二个命令行参数就是保存剪贴板当前内容的关键字。关键字将用做 mcbShelf中的键,值就是当前剪贴板上的文本❷。

如果只有一个命令行参数,就假定它要么是'list',要么是需要加载到剪贴板的关键字。稍后你将实现这些代码。现在只是放上一条TODO注释❸。

第3步:列出关键字和加载关键字的内容

最后,让我们实现剩下的两种情况。用户希望从关键字加载剪贴板文本,或希望列出所有可用的关键字。让你的代码看起来像这样:

 #! python3 # mcb.pyw - Saves and loads pieces of text to the clipboard. --snip-- # Save clipboard content. if len(sys.argv) == 3 and sys.argv[1].lower == 'save': mcbShelf[sys.argv[2]] = pyperclip.paste elif len(sys.argv) == 2:     # List keywords and load content.❶     if sys.argv[1].lower == 'list':❷ pyperclip.copy(str(list(mcbShelf.keys)))    elif sys.argv[1] in mcbShelf:❸ pyperclip.copy(mcbShelf[sys.argv[1]])  mcbShelf.close  

如果只有一个命令行参数,首先检查它是不是'list' ❶。如果是,表示shelf键的列表的字符串将被拷贝到剪贴板❷。用户可以将这个列表拷贝到一个打开的文本编辑器,进行查看。

否则,你可以假定该命令行参数是一个关键字。如果这个关键字是shelf中的一个键,就可以将对应的值加载到剪贴板❸。

齐活了!加载这个程序有几个不同步骤,这取决于你的计算机使用哪种操作系统。请查看附录B,了解操作系统的详情。

回忆一下第6章中创建的口令保管箱程序,它将口令保存在一个字典中。更新口令需要更改该程序的源代码。这不太理想,因为普通用户不太适应通过更改源代码来更新他们的软件。而且,每次修改程序的源代码时,就有可能不小心引入新的缺陷。将程序的数据保存在不同的地方,而不是在代码中,就可以让别人更容易使用你的程序,并且更不容易出错。

8.7 小结

文件被组织在文件夹中(也称为目录),路径描述了一个文件的位置。运行在计算机上的每个程序都有一个当前工作目录,它让你相对于当前的位置指定文件路径,而非总是需要完整路径(绝对路径)。os.path模块包含许多函数,用于操作文件路径。

你的程序也可以直接操作文本文件的内容。open函数将打开这些文件,将它们的内容读取为一个大字符串(利用reae方法),或读取为字符串的列表(利用方法readlines)。Open函数可以将文件以写模式或添加模式打开,分别创建新的文本文件或在原有的文本文件中添加内容。

在前面几章中,你利用剪贴板在程序中获得大量文本,而不是通过手工输入。现在你可以用程序直接读取硬盘上的文件,这是一大进步。因为文件比剪贴板更不易变化。在下一章中,你将学习如何处理文件本身,包括复制、删除、重命名、移动等。

8.8 习题

1.相对路径是相对于什么?

2.绝对路径从什么开始?

3.os.getcwd和os.chdir函数做什么事?

4..和..文件夹是什么?

5.在C:/bacon/eggs/spam.txt中,哪一部分是目录名称,哪一部分是基本名称?

6.可以传递给open函数的3种“模式”参数是什么?

7.如果已有的文件以写模式打开,会发生什么?

8.read和readlines方法之间的区别是什么?

9.shelf值与什么数据结构相似?

8.9 实践项目

作为实践,设计并编写下列程序。

8.9.1 扩展多重剪贴板

扩展本章中的多重剪贴板程序,增加一个delete <keyword>命令行参数,它将从shelf中删除一个关键字。然后添加一个delete命令行参数,它将删除所有关键字。

8.9.2 疯狂填词

创建一个疯狂填词(Mad Libs)程序,它将读入文本文件,并让用户在该文本文件中出现ADJECTIVE、NOUN、ADVERB或VERB等单词的地方,加上他们自己的文本。例如,一个文本文件可能看起来像这样:

The ADJECTIVE panda walked to the NOUN and then VERB. A nearby NOUN wasunaffected by these events.  

程序将找到这些出现的单词,并提示用户取代它们。

Enter an adjective:sillyEnter a noun:chandelierEnter a verb:screamedEnter a noun:pickup truck  

以下的文本文件将被创建:

The silly panda walked to the chandelier and then screamed. A nearby pickuptruck was unaffected by these events.  

结果应该打印到屏幕上,并保存为一个新的文本文件。

8.9.3 正则表达式查找

编写一个程序,打开文件夹中所有的.txt文件,查找匹配用户提供的正则表达式的所有行。结果应该打印到屏幕上。