程序要有所作为,就需要与周遭世界通信。它需要与用户交互、读写文件、访问网页等。通常,我们称之为输入和输出(简称I\O)。
你见识过基本的控制台IO,这包括打印消息以及使用函数input读取用户输入的字符串,本章首先介绍一些设置字符串格式的方法,让你能够通过控制台IO输出美观的字符串。
接下来,我们将把注意力转向文件IO,即读写文件。Python提供了强大的基本文件IO支持,最大限度地简化了程序员的工作。具体地说,我们将介绍如何使用文本文件、二进制文件以及功能强大的pickle模块。
Python提供了很多设置字符串格式的方式,这里将讨论较老的字符串插入和较新的格式字符串。
字符串插入是一种设置字符串模式的简单方法,是Python从编程语言C那里借鉴而来的。例如, 下面的示例演示了如何控制小数位数:
字符串插入表达式总是采用这样的格式: format % values, 其中format 是包含一个或多个%字符的字符串。在示例‘value: %.2f’% x, 子串%.2f是一个格式设置命令,让Python获取后面提供的第一个值(x),并将其显示为包含两位小数的浮点数。
在前面的格式字符串,f是一个转换说明符,告诉Python如何显示相应的值。表8-1列出了最常用的转换说明符。
可根据 需要在格式字符串中包含任意数量的说明符,但必须为每个说明符提供一个值。例如:
从这个示例可知,格式字符串相当于简单模板,将用指定的值填充。值是以元组方式指定的,将用指定的值 填充。值是以元组方式指定的,它们的排列顺序必须与替换顺序一到致。
在Python中,另一种创建美观字符串的方式是结合使用格式字符串和字符串函数format(value, format_spce)。例如:
在格式字符串中,用大括号括起的内容都将被替换,这称为命令替换(named replacement)。就这个示例而言,命名替换的可读性极佳。
文件是一个命名的比特集合,存储在硬盘、U盘、闪存条等辅助存储设备中。这里将文件分为两类:文本文件和二进制文件,其中前者本质上是存储在磁盘中的字符串,而后者是其他各种内容。
*基本上是磁盘中的字符串。Python源代码文件和HTML文件都属于文本文件。
*可使用任何文本编辑器进行编辑,因此对人类来说相对容易阅读和修改。
*对程序来说,它们通常难以阅读。通常,每种文本文件都需使用相应的分析程序(parser)来阅读,例如,Python使用专用分析程序来帮助阅读.py文件,而要阅读HTML文件,需要使用专用于HTML的分析程序。
*通常比等价的二进制文件大。需要通过网络发送大型文本文件时,这是个严重的问题。因此,通常对文本文件进行压缩(如压缩成zip格式),以提高传输速度和节省磁盘空间。
*通常是人类无法阅读的,至少使用常规文本编辑器无法查看。在文本编辑器中打开二进制文件(如JPEG图像)需要使用特殊查看器来显示其内容。
*占据的空间通常比等价的文本文件小。例如,二进制可能将其保存的信息编组,每组包含32位,而两组之间不使用逗号或空格等分隔符。
*对程序来说,它们读写起来通常比文本文件容易。虽然二进制文件各不相同,但通常无需编写复杂的分析程序列来读取它们。
*通常与特定程序相关联,如果没有该程序,通常无法使用它们。有些流行的二进制文件的格式是公开的,因此如果你愿意,可以自己编写读写它们的程序,但通常需要花很大的功夫。
除文件外,人们还使用文件夹(目录)来存储文件和其他文件夹。大多数文件系统的文件夹结构都庞大而复杂,呈层次型。
Python提供了很多这样的函数:返回有关计算机文件系统中文件和文件夹的信息。表8-2列出了其中最实用的几个。
下面来编写几个函数,看看上述函数的工作原理。例如,一种常见的任务是获悉当前工作目录中的文件和文件夹。代码os.listdir(os.getcwd())比较难看,因此我们编写下面这样的函数:
#list.py
import os
def list_cwd():
return os.listdir(os.getcwd())
下面是两个相关的辅助函数,它们使用列表解析分别返回当前工作目录中的文件和文件夹:
#list2.py
import os
def list_cwd():
return os.listdir(os.getcwd())
def files_cwd():
return [p for p in list_cwd()
if os.path.isfile(p)]
def folders_cwd():
return [p for p in list_cwd()
if os.path.isdir(p)]
#list3.py
import os
def list_py(path = None):
if path == None:
path = os.getcwd()
return [fname for fname in os.listdir(path)
if os.path.isfile(fname)
if fname.endswith('.py')]
这个函数巧妙地利用了其输入参数:如果你调用list_py()时未提供参数,它将把当前工作目录视为目标文件夹,否则将指定的目录视为目标文件夹。
#list_size.py
import os
def size_in_bytes(fname):
return os.stat(fname).st_size
def cwd_size_in_bytes():
total = 0
for name in files_cwd():
total = total + size_in_bytes(name)
return total
在Python中,处理文本文件相对容易。通常采用图8-1所示的3个步骤来处理文件。
读取文本文件的常见方式可能是每次读取一行。例如,下面的代码在屏幕上打印文件的内容:
#printfile.py
def print_file1(fname):
f = open(fname, 'r')
for line in f:
print(line, end = '')
f.close() #这行代码是可选的
这个函数的第1行打开指定的文件:调用函数 open时,必须指定你要处理的文件的名称,还必须指定打开模式。这里只读取文件,因为以读取模式('r')打开它。表8-3列出了Python中主要的文件打开模式。
函数open返回一个特殊的文件对象,表示磁盘中的文件。最重要的是,open不将文件读取到内存中。在上述示例中,使用for循环以每次一行的方式读取文件。函数print_file1的最后一行关闭文件;正如注释指出的,这是可选的,因为Python几乎总是会自动为你关闭文件。在这里,f是函数print_file1结束中的一个局部变量,因为函数print_file1结束时, Python将自动关闭并删除f指向的文件对象(不是文件本身)。
另一种读取文本文件的常见方式是,将其作为一个大型字符串进行读取,如下所示:
#prinfile2.py
def print_file2(fname):
f = open(fname, 'r')
print(f.read())
f.close()
这比函数print_file1短小、简单,很多程序员都喜欢彩这种方式。然后,如果要读取的文件非常大,将占用大量内存,这可能降低计算机的运行速度,甚至导致计算机崩溃。
print(open(fname, 'r').read())
这种形式更紧凑,可能需要一段时间才能习惯,但很多程序员都喜欢这种风格,因为其输入量少,可读性也相对较高。
写入文本文件只比读取文本文件复杂一点点,例如,下面这个函数新建一个名为story.txt的文本文件:
#write.py
def make_story1():
f = open('story.txt', 'w')
f.write('Mary had a little lamb, \n')
f.write('and then she had some more.\n')
#write.py
import os
def make_story1():
f = open('story.txt', 'w')
f.write('Mary had a little lamb,\n')
f.write('and then she had some more.\n')
'w'让Python以写入模式打开文件。为将文本写入文件,你调用f.write,并将要写入的字符串传递给它。字符串将以指定的顺序写入文件。
需要注意的是,如果文件story.txt已经存在,调用open('story.txt', 'w')将删除它!如果你不想覆盖story.txt,应先检查它是否存在:
#write2.py
import os
def make_story2():
if os.path.isfile('story.txt'):
print('story.txt already exists')
else:
f = open('story.txt', 'w')
f.write('Mary had a little lamb,\n')
f.write('and then she had some more.\n')
相比于在文件末尾附加字符串,将字符串写入文件开头不那么容易,因为操作系统Windows, Linux和Macintosh都没有为这样做提供直接支持。要将文件插入到文件开头,最简单的方式可能如上:将文件读到一个字符串中,将磨擦文本插入到该字符串,再将这个字符串写入原来的文件,如下所示:
#insert_title.py
import os
def insert_title(title, fname = 'story.txt'):
f = open(fname, 'r+')
temp = f.read()
temp = title + '\n\n' + temp
f.seek(0) #让文件指针指向文件开头
f.write(temp)
首先,注意到我们使用了特殊模式‘r+’来打开文件,这意味着可读取和写入文件。接下来,我们将整个文件读取到字符串变量temp中,并使用字符串拼接插入标题(title)。
将新创建的字符串写回文件前,必须先让文本文件对象都记录了它当前指向文件的什么位置,调用f.read()后,文件指针指向文件末尾。通过调用f.seek(0),让文件指针重新指向了文件开头,这样写入f时,将从文件开头开始。
如果文件不是文本文件,它就被视为二进制文件。二进制文件以模式'b'打开,而你可访问其各个字节。例如
#is_gif.py
import os
def is_gif(fname):
f = open(fname, 'br')
first4 = tuple(f.read(4))
print(first4)
return first4 == (0X47, 0X49, 0X46, 0X38)
这个函数检查fname是不是GIF图像文件,方法是检查其前4个字节是不是(0x47, 0x49, 0x46, 0x38)(所有GIF图像文件都以这4个字节打头)。
在Python中,类似于0x47的数据为十六进制数。十六进制数非常适合用于处理字节,因为每个十六进制位对应于4比特,因此使用两个十六进制位(0X47)可描述一个字节。
注意到文件是以'br'模式打开的,这表示二进制读取模式。读取二进制文件时,调用f.read(n)来读取接下来的n个字节。与文本文件对胆一要,二进制文件对象也使用文件指针来记录接下来应读取文件的哪个字节。
访问二进制文件的各个字节是一种非常低级的操作,虽然在系统编程中很有用,但在较高级的应用程序编程中用途有限。
在处理二进制文件方面,pickle通常是一种方便很多的方式。Python模块pickle让你能够轻松地读写几乎任何数据结构,如下所示:
#picklefile.py
import pickle
def make_pickled_file():
grades={'alan':[4, 8, 10, 10],
'tom':[7, 7, 7, 8],
'dan':[5, None, 7, 7],
'may':[10, 8, 10, 10]}
outfile = open('grades.dat', 'wb')
pickle.dump(grades, outfile)
def get_pickled_data():
infile = open('grades.dat', 'rb')
grades = pickle.load(infile)
return grades
基本上,你使用pickle.dump将数据结构存储到磁盘,以后再使用pickle.load从磁盘获取数据结构。对很多程序来说,这都是一项特别有用的功能,因此每当需要存储二进制数据时,都应考虑使用这种功能。
Python为访问网络提供了强大支持。一种常见的任务是让程序自动读取网页,而使用模块urllib可轻松地完成这种任务:
html包含www.python.org处网页的全部文本。这些文本是HTML格式的,因此与你在Web浏览器中使用“查看源代码”选项看到的结果一样。将网页作为字符串存储到计算机后,但可使用Python字符串操作函数提取其中的信息。
另一个绝妙的模块是webbrowser,它让你能够以编程方式在浏览器中显示网页。例如,下述代码是在默认Web浏览器中显示CSDN的主页: