小段落


7. 输入(Input)与输出(Output)

有很多的方式可以来表现一个程式的输出结果,可以印出来在一个可读的表格里面,也可以写入到档案里面供作未来使用。这一章里面将谈到一些可能的方法。


7.1 花俏的输出格式化

到现在为止我们谈到了两种写入值的方式:用expression的叙述( expression statements ),或是用 print 这个叙述。 (第三种方法是使用file物件的 write() 方法(method),这一个标准输出所指向的档案(standard output file),可以用 sys.stdout 来存取之。请参阅程式库参考手册上面对此的详细说明。)

通常你会希望你对于输出的结果能够在格式上面稍有控制力,而不只是预设的用空白连结起来而已。有两种方法可以来控制输出的格式,第一种是自己动手来做字串的调整。你可以使用字串的切割(slicing)以及连结,做成任何你想要的效果。标准的 string module里面有一些好用的东西,也可以帮助你填入适当的空白,使字串的宽度成为你想要的宽度,我们待会再来讨论如何做。另外一个控制输出格式的方法是使用 % 这个运算元,配合上用字串成为左边的参数。这个运算元会翻译左边的这个字串参数,其功能类似于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...
>>> # Reverse quotes work on other types besides numbers:
... p = [x, y]
>>> ps = repr(p)
>>> ps
'[31.4, 40000]'
>>> # Converting a string adds string quotes and backslashes:
... hello = 'hello, world\n'
>>> hellos = `hello`
>>> print hellos
'hello, world\012'
>>> # The argument of reverse quotes may be a tuple:
... `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),
... # Note trailing comma on previous line
... 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() 这个函式会使的数目字的字串加入前头的0。该加入正负号的时候它也会自动加入:

>>> import string
>>> 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.

如果在你的格式化字串(format string)中有超过一个以上的格式存在,你要在 % 的右边传入一个tuple。例如这个例子:

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

大部分的格式(format)其效果都与你在C里面所用的一样,你必须要在右边传入适当型态的资料。如果你没有正确的如此做时,你会得到一个例外的状况(exception),而不是得到一个系统核心倾倒出来的记忆体资料(dump)。其中 %s 这个格式最为自由,你可以使用字串或非字串,如果你使用非字串的资料时,资料会自动用内建的 str() 函式转换成字串资料。你也可以使用 * 来传入一个独立的(整数)参数来决定宽度或是精确度(precision)的大小。但是,C里面的 %n 以及 %p 在Python里面却没有支援。

如果你有一个很长的格式化字串,而你又不想分开他们的话,你可以使用名称而非位置来使用这些变数。其方法是使用C格式的延伸形式: %(name)format ,举例如下:

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

这个功能当与新的内建函式 vars() 一起使用时特别有用,这个内建函式会传回一个含有所有local变数名称及值的dictionary。


7.2 读写档案

open() 这个函式会传回一个file物件。通常其用法是传入两个参数如: "open(filename, mode)".

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

第一个参数是一个包含档案名称的字串,第二个参数是另外一个字串,其内容是一些用来描述你要怎么使用这个档案的字元。 mode 可以是 'r' ,如果你想要这个档为唯读的话,也可以使用 'w' 如果你只想要写入的话(如果该档本来就存在的话,你会杀掉原来的档案),你也可以用 'a' 表示你要在档案的尾端加入东西, 'r+' 则会让这个档可以读也可以写。你也可以不传入第二个参数,如果没有传入 mode 参数的话,会使用预设的 'r' 模式。

在Windows以及Macintosh系统上,你可以在mode里面加入 'b' 表示要以二元模式(binary mode)开启这个档案,所以你也可以使用 'rb', 'wb', 以及 'r+b' 。在Windows里面文字档及二元档是有区别的,在文字档里面行终止字元(end-of-line)在档案的读写时是自动会被稍稍修改的。这个自动修改的动作对于一般的ASCII文字档没有什么影响,但是会使得像是 JPEGs 或是 .EXE 之类的二元档被损害。所以当你在处理这些档案时特别注意要使用二元的模式。(值得注意的是,在Macintosh里面文字模式的精确的语意是会随着其背后所用的C程式库而有不同的。)


7.2.1 File物件的Methods(方法)

底下的例子都假设你已经建立了一个叫做 f 的file物件。

如果你想读一个档案的内容你需要呼叫 f.read(size) 这个方法(method)。这个method会读入某个数量的资料,然后将资料以字串的形式传回。你也可以不传入 size 这个数值参数,如果你没有传入或是传入负值的话,就会将整个档案都传回。如果你的档案比你的记忆体的两倍还大的话,这是你自己要处理的问题。其他的情况下,都会读入并传回最多是 size 数量的位元组(byte)的资料。如果已经到了档案的最尾端你还呼叫 f.read() 的话,回传值就会是一个空字串 ("") 。

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

f.readline() 会一次只读入一行,换行符号 (\n ) 仍然会被留在字串的最尾端,并且当档案不是以换行符号结束时,最后一行的换行符号就会被忽略。这会使得传回的结果不至于有混淆,当传回值是空字串时,我们可以很有信心这已经是档案的最尾端,因为空白的行还是会有 '\n' 单独存在的。

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

f.readlines() 会传回一个 list ,其内容是所有在档案内的各个行的资料。如果你传入第二个可有可无的 sizehint 参数时,会从档案内读入这个参数所代表的byte数目,并且把最后所在的那一整行也一并读完。这一个方法通常用在一行一行的读很大档案时,如此可以增进读的效率,并避免在记忆体中放置大量的资料。只有完整的行才会被传回来。

>>> 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() 会传回一个整数,代表目前这个file物件在这个档案内的所在位置,其单元是从档案开始处有多少个byte。你可以用 "f.seek(offset, from_what)" 来改变file物件的所在位置, from_what 参数代表从哪里算起,0代表档案的最开头,1代表目前位置,2代表档案的结尾处。呼叫这个函式file物件会跳到从 from_what 参数代表的位置算起 offset 个byte的距离的地方。如果 from_what 没有传入的话,会使用预设的 0,代表从档案的最开头算起。

>>> f=open('/tmp/workfile', 'r+')
>>> f.write('0123456789abcdef')
>>> f.seek(5) # Go to the 5th byte in the file
>>> f.read(1)
'5'
>>> f.seek(-3, 2) # Go to the 3rd byte before the end
>>> f.read(1)
'd'

当你已经使用完毕这个file物件时,要记得呼叫 f.close() 把所有因为开档所使用的系统资源都释放掉。一但你呼叫了 f.close() 之后,任何的对file物件的动作都会自动的失败。

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

File 物件有一些其他的method可以用,例如 isatty() 以及 truncate() ,这些比较少用的method可以参考在程式库参考手册里面有关file物件的说明。


7.2.2 pickle Module(模组)

从档案写入及读出字串资料都没有太大问题,但是数值资料则会比较麻烦。因为 read() 这个method 只传回字串,你还得要将这个字串传给类似 string.atoi() 这样的函式来将代表数值的字串 '123' 转成数值123。如果你要在档案内储存较复杂的资料型态例如lists、dictionaries、或是某个类别的物件时,那就更加复杂了。

为使使用者不需要自己写程式来处理储存这些复杂的资料型态,Python提供了一个标准的module叫做 pickle 。这个令人惊讶的module可以处理几乎所有的Python物件(甚至是某些形式的Python程式码!),并将之转换成一个字串的表现方式。这个过程也叫做 pickling. R。从这个字串重新组合成我们所要的物件的过程则叫做 unpickling 。在这两个过程之间,我们可以将这个代表物件的字串储存成档案或资料,或是在网路上传给另一台机器。

如果你有一个 x 物件及一个可以写入的file物件 f ,要pickle一个物件最简单的方式只要一行程式就可以了:

pickle.dump(x, f)

如果file物件 f 是可读的话,要unpickle这个物件只要这样做:

x = pickle.load(f)

(这个module还有其他的用法可以pickling多个物件,或是你不想将这个pickled的资料写入档案。请参考在程式库参考手册内有关 pickle 完整的说明。)

pickle 也是一个标准的方法,可以将Python的物件储存起来给其他程式语言使用,或是等待下一次启动Python再用。技术上来说这叫做 persistent 的物件。因为 pickle 的运用如此广泛,许多的程式设计师专门写一些Python的延伸功能来处理诸如matrices这些新资料型态的pickle 以及 unpickle的过程。


请看关于此文件… 里面有关如何给我们建议的说明。