第七章 输入输出

有几种办法可以从程序输出;数据可以用可读的形式显示,或保存到文件中以备日后使用 。本章讨论一些输入输出的办法。

7.1 输出格式控制

到现在为止我们已经看到了两种输出值的方法:表达式语句和print语句。(第三种方法 是使用文件对象的write()方法,标准输出文件可以用sys.stdout引用。参见库参考手册)。

我们常常需要控制输出格式,而不仅仅是显示空格分开的值。有两种办法控制输出格式: 一种办法是自己进行字符串处理,用字符串的片断和合并操作可以产生任何可以想象的格式 。标准模块string包含了诸如把字符串填充到指定的列宽这样的有用操作,后面会有提及。

另一种办法是使用%运算符,此运算符以一个字符串为左运算元,它按C的sprintf()函数 格式把右运算元转换为字符串,返回转换结果。

  问题是:如何把值转换为字符串?

幸运的是,Python有一种办法可以把任何值转换为字符串:使用repr()函数,或把值写在 两个反向引号(``)之间。例如:

>>> x = 10 * 3.14
>>> y = 200*200
>>> s = 'The value of x is ' + `x` + ', and y is ' + `y` + '...'
>>> print s
The value of x is 31.4, and y is 40000...
>>> # 反向引号也适用于非数值型
... p = [x, y]
>>> ps = repr(p)
>>> ps
'[31.4, 40000]'
>>> # 转换字符串对字符串加字符串引号和反斜杠
... hello = 'hello, world\n'
>>> hellos = `hello`
>>> print hellos
'hello, world\012'
>>> # 反向引号内可以是一个序表
... `x, y, ('spam', 'eggs')`
"(31.4, 40000, ('spam', 'eggs'))"

下面是两种写出平方、立方表的方法:

>>> import string
>>> for x in range(1, 11):
...     print string.rjust(`x`, 2), string.rjust(`x*x`, 3),
...     # 前一行的结尾逗号表示不换行
...     print string.rjust(`x*x*x`, 4)
...
 1   1    1
 2   4    8
 3   9   27
 4  16   64
 5  25  125
 6  36  216
 7  49  343
 8  64  512
 9  81  729
10 100 1000
>>> for x in range(1,11):
...     print'%2d %3d %4d' % (x, x*x, x*x*x)
... 
 1   1    1
 2   4    8
 3   9   27
 4  16   64
 5  25  125
 6  36  216
 7  49  343
 8  64  512
 9  81  729
10 100 1000

注意print输出的各项之间额外加了一个空格,这是print的规定。  

此例显示了函数string.rjust()的用法,此函数可以把一个字符串放进指定宽度右对齐, 左边用空格填充。类似函数还有string.ljust()和string.center()。这些函数不向外输出, 只是返回转换后的字符串。如果输入字符串太长也不会被截断而是被原样返回。这样的处理 可能会使你的列对齐失效,但这可能比截断要好一些,截断的结果是我们看到一个错误的值 。(如果你确实需要截断的话总可以再加一层片断,如string.ljust(x,n)[0:n])。

  还有一个函数string.zfill(),可以在数值左边填零。此函数可以处理带有加减号的情况:

>>> string.zfill('12', 5)
'00012'
>>> string.zfill('-3.14', 7)
'-003.14'
>>> string.zfill('3.14159265359', 5)
'3.14159265359'

%操作符的用法如下例:

>>> import math
>>> print 'The value of PI is approximately %5.3f.' % math.pi
The value of PI is approximately 3.142.

如果有多个值可以用一个序表给出,这时格式字符串中要有多个格式,如:

>>> table = {'Sjoerd': 4127, 'Jack': 4098, 'Dcab': 8637678}
>>> for name, phone in table.items():
...     print'%-10s ==> %10d' % (name, phone)
... 
Jack       ==>       4098
Dcab       ==>    8637678
Sjoerd     ==>       4127

大多数格式与C用法相同,要求要输出的值的类型符合格式的需要。但是,如果你没有引 发例外错误的话也不会产生内核堆列。Python的%s格式要宽得多:如果相应的输出项不是字 符串对象,就先用str()内置函数把它变成字符串。在格式指定中宽度指定为*号表示后面的 输出项中多出一个指定宽度的整数。C格式%n和%p未被支持。

如果你有一个长格式串不想把它分开,可以在指定格式的地方指定名字,这样就不需要按 次序去把格式和名字对应起来,这种格式为“%(变量名)格式”,例如:

>>> table = {'Sjoerd': 4127, 'Jack': 4098, 'Dcab': 8637678}
>>> print 'Jack: %(Jack)d; Sjoerd: %(Sjoerd)d; Dcab: %(Dcab)d' % table
Jack: 4098; Sjoerd: 4127; Dcab: 8637678

这里输出项总是一个字典,字典的各项值是要输出的值,字典的键值是各值的名字。这种 输出格式经常与内置函数var()配合使用,var()返回包含所有局部变量的字典。

7.2 读写文件

  open()打开一个文件对象,经常使用两个参数:“open(文件名,模式)”。例如:

>>> f=open('/tmp/workfile', 'w')
>>> print f
<open file '/tmp/workfile', mode 'w' at 80a0960>

第一自变量是一个包含了文件名的字符串,第二自变量是文件打开方式的字符串。模式‘r ’表示读取,‘w’表示只写(已有的同名文件被清除),‘a’表示打开文件在尾部添加, ‘r+’表示打开文件既可以读也可以写。打开方式参数可选,缺省为‘r’模式。

在Windows和Macintosh中在模式中加入‘b’表示以二进制格式打开文件,如‘rb’、‘wb ’、‘r+b’。Windows对文本文件和二进制文件有不同的处理,文本文件中的换行字符在读 写时有变化。这种对文件数据的幕后的修改不影响ASCII文本文件,但是会破坏二进制数据如JPEG 或“.EXE”文件的数据。读写这样的文件一定要使用二进制格式。(Macintosh中文本模式的 精确描述依赖于使用的C库)。

7.2.1 文件对象的方法

  本节后面的例子假设已经建立了一个名为f的文件对象。

为了读取文件内容,调用f.read(size),可以读入一定字节数的数据返回为一个字符串。size 是一个可选数值参数,省略size或size取负值时读入整个文件并返回为一个字符串;如果文 件比你的机器内存大一倍,那是你的问题。指定了正的size的时候之多读入并返回size字节 。如果读到了文件尾,f.read()返回一个空串("")。如:

 
>>> f.read()
'This is the entire file.\012'
>>> f.read()
''
 

f.readline()从文件中读入一行,返回的字符串中将包括结尾的一个换行符(\n),如果 文件的最后一行没有换行符则由该行读入的字符串也没有结尾的换行符。这样,由readline() 返回的结果不会有歧义,读入结果为空串时一定是到了文件尾,读入一个'\n'时为空行。

>>> f.readline()
'This is the first line of the file.\012'
>>> f.readline()
'Second line of the file\012'
>>> f.readline()
''

f.readlines()反复调用f.readline(),返回一个包含文件所有行的列表。

>>> f.readlines()
['This is the first line of the file.\012', 'Second line of the file\012']

f.write(string)把string的内容写入到文件中,返回None。
 
>>> f.write('This is a test\n')
 

f.tell()返回文件对象的当前读写为止,按从文件开始的字节数算。为了改变读写位置, 使用“f.seek(位移,从哪里)”。读写位置按一个参考点加上位移来计算,参考点用“从那 里”参数指定,取0时从文件头开始算,取1时按当前位置算,取2时从文件尾算。缺省值是0 ,从文件开始算。

>>> f=open('/tmp/workfile', 'r+')
>>> f.write('0123456789abcdef')
>>> f.seek(5)     # 从文件头前进5个字节,到达第6个字符
>>> f.read(1)        
'5'
>>> f.seek(-3, 2) # 转到结尾前3个字符
>>> f.read(1)
'd'

用外一个文件后调用f.close()关闭文件,释放打开文件所占用的系统资源。文件关闭后 再使用此文件对象就无效了。

>>> f.close()
>>> f.read()
Traceback (innermost last):
  File "<stdin>", line 1, in ?
ValueError: I/O operation on closed file

文件对象还有其它一些不太常用的方法,例如isatty()和truncate(),参见库参考手册。  

7.2.2 pickle模块

字符串可以很容易地从文件读入或向文件写出。读入数值要麻烦一些,因为read()方法总 是返回字符串,要把读入的字符串传给象string.atoi()这样的函数,把象‘123’这样的字 符串转换为对应的整数值123。但是,当你想保存更复杂的数据类型如列表、字典或类实例时 ,读写就要复杂得多。

Python的设计使程序员可以不必反复编写调试保存复杂数据类型的代码,它提供了一个叫 做pickle的标准模块。这个令人惊异的模块可以把几乎任何Python对象转换为字符串表示, 这个过程叫做腌制,从对象的字符串表示恢复对象叫做恢复。在腌制和反腌制之间,对象的 字符串表示可以保存在文件或数据中,甚至于通过网络连接传送到远程计算机上。

  如果你有一个对象x,有一个可写的文件对象f,最简单的腌制对象的办法是下面一行代码:

pickle.dump(x, f)

为了恢复对象,如果刚才的文件已打开用于读取,文件对象名仍为f,则:

x = pickle.load(f)

(腌制和恢复还有其它用法,可以腌制多个对象,可以不把数据写入文件,详见库参考手 册)。

pickle是保存Python对象并被其它程序或同一程序以后再运行时调用的标准办法,这种做 法的专用术语叫做“持久对象”。因为pickle使用广泛,许多Python扩展模块的作者都留意 使新增加的数据类型如矩阵可以正确地腌制和恢复。