7. 输入和输出

展现程序的输出有多种方法;可以打印成人类可读的形式,也可以写入到文件以便后面使用。本章将讨论其中的几种方法。

7.1.胡思乱想输出格式

到目前为止我们遇到过两种输出值的方法:表达式语句print语句。(第三个方式是使用文件对象的write()方法;标准输出文件可以引用 sys.stdout详细内容参见库参考手册。)

通常你会希望更好地控制输出的格式而不是简单地打印用空格分隔的值。有两种方法来设置输出格式;第一种方式是自己做所有的字符串处理;使用字符串切片和连接操作,你可以创建任何你能想象到的布局。字符串类型有一些方法,用于执行将字符串填充到指定列宽度的有用操作;这些稍后将讨论。第二种方法是使用str.format()方法。

string模块包含一个Template类,提供另外一种向字符串代入值得方法。

当然还有一个问题:如何将值转换为字符串?幸运的是,Python 有方法将任何值转换为字符串:将它传递给repr()str()函数。

str()函数的用意在于返回人类可读的表现形式,而repr()的用意在于生成解释器可读的表现形式(如果没有等价的语法将会引发SyntaxError异常)。对于对人类并没有特别的表示形式的对象, str()repr()将返回相同的值。许多值,例如数字或者列表和字典这样的结构,使用这两个函数中的任意一个都具有相同的表示形式。特别地,字符串和浮点数有两种不同的表示形式。

一些例子:

>>> s = 'Hello, world.'
>>> str(s)
'Hello, world.'
>>> repr(s)
"'Hello, world.'"
>>> str(1.0/7.0)
'0.142857142857'
>>> repr(1.0/7.0)
'0.14285714285714285'
>>> x = 10 * 3.25
>>> y = 200 * 200
>>> s = 'The value of x is ' + repr(x) + ', and y is ' + repr(y) + '...'
>>> print s
The value of x is 32.5, and y is 40000...
>>> # The repr() of a string adds string quotes and backslashes:
... hello = 'hello, world\n'
>>> hellos = repr(hello)
>>> print hellos
'hello, world\n'
>>> # The argument to repr() may be any Python object:
... repr((x, y, ('spam', 'eggs')))
"(32.5, 40000, ('spam', 'eggs'))"

这里用两种方法输出平方和立方表:

>>> for x in range(1, 11):
...     print repr(x).rjust(2), repr(x*x).rjust(3),
...     # Note trailing comma on previous line
...     print repr(x*x*x).rjust(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 '{0:2d} {1:3d} {2:4d}'.format(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自动添加:它总会在它的参数之间添加空格。)

上面的例子演示了字符串对象的str.rjust()方法,它通过在左侧填充空格使字符串在给定宽度的列右对齐。类似的方法还有str.ljust()str.center()这些方法不会输出任何内容,它们只返回新的字符串。如果输入的字符串太长,它们不会截断字符串,而是保持原样返回;这会使列的格式变得混乱,但是通常好于另外一种选择,那可能是一个错误的值。(如果你真的想要截断,可以加上一个切片操作,例如x.ljust(n)[:n]。)

另外一种方法 str.zfill(),它向数值字符串左侧填充零。该函数可以正确识别正负号:

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

str.format()方法的基本用法如下所示:

>>> print 'We are the {} who say "{}!"'.format('knights', 'Ni')
We are the knights who say "Ni!"

花括号及其中的字符(称为格式字段)将被替换为传递给str.format()方法的对象。括号中的数字指传递给str.format()方法的对象的位置。

>>> print '{0} and {1}'.format('spam', 'eggs')
spam and eggs
>>> print '{1} and {0}'.format('spam', 'eggs')
eggs and spam

如果str.format()方法使用关键字参数,那么将通过参数名引用它们的值。

>>> print 'This {food} is {adjective}.'.format(
...       food='spam', adjective='absolutely horrible')
This spam is absolutely horrible.

位置参数和关键字参数可以随意组合:

>>> print 'The story of {0}, {1}, and {other}.'.format('Bill', 'Manfred',
...                                                    other='Georg')
The story of Bill, Manfred, and Georg.

'!s'(适用str())和'!r'(适用repr())可以用于值被格式化之前对值进行转换。

>>> import math
>>> print 'The value of PI is approximately {}.'.format(math.pi)
The value of PI is approximately 3.14159265359.
>>> print 'The value of PI is approximately {!r}.'.format(math.pi)
The value of PI is approximately 3.141592653589793.

字段名后允许可选的':'和格式指令。这允许更好地控制如何设置值的格式。下面的例子将 Pi 转为三位精度。

>>> import math
>>> print 'The value of PI is approximately {0:.3f}.'.format(math.pi)
The value of PI is approximately 3.142.

':'后面紧跟一个整数可以限定该字段的最小宽度。这在美化表格时很有用。

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

如果你有一个实在是很长的格式字符串但又不想分开写,要是可以按照名字而不是位置引用变量就好了。有个简单的方法,可以传入一个字典,然后使用'[]'访问。

>>> table = {'Sjoerd': 4127, 'Jack': 4098, 'Dcab': 8637678}
>>> print ('Jack: {0[Jack]:d}; Sjoerd: {0[Sjoerd]:d}; '
...        'Dcab: {0[Dcab]:d}'.format(table))
Jack: 4098; Sjoerd: 4127; Dcab: 8637678

这也可以用‘**’符号将这个字典以关键字参数的方式传入。

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

这是在结合内置函数vars(),它返回一个包含所有局部变量的字典中尤其有用。

字符串格式设置与str.format()的完整概述,请参见格式字符串语法

7.1.1.旧的字符串格式

%运算符还可以用于字符串格式设置。它解释很像sprintf()的左的参数-样式的格式字符串应用于右侧参数,并返回此格式设置操作所引起的字符串。例如:

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

字符串格式设置操作一节中,可以发现更多的信息。

7.2.读取和写入文件

open ()返回一个文件对象,并且最常用的具有两个参数:打开 (文件名, 模式)

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

第一个参数是一个包含文件名的字符串。第二个参数是另一个字符串,其中包含几个字符描述将在其中使用该文件的方式。模式可以是'r'时将只读取文件,只写w (具有相同名称的现有文件将被删除),和'a'打开该文件进行追加 ;写入到文件中的任何数据将自动添加到末尾。 ' r +'打开文件进行读取和写入。模式参数是可选的 ;如果它将被省略,将假定'r'

在 Windows 上, 'b'追加模式打开的文件在二进制模式中,所以也有像'理' 'wb',和'r + b'模式。Python 在 Windows 上的进行了区分文本和二进制文件 ;当读取或写入数据时,文本的文件中的行尾字符是自动略有改变。这一幕后修改的文件数据是 ASCII 文本文件,但它会在JPEGEXE文件中像这样腐败的二进制数据。要使用二进制模式读取和写入此类文件时非常小心。在 Unix 平台上,在模式后面附加一个'b'也不会有坏处,这样你可以用写好的文件访问代码来读写任何平台上的所有二进制文件。

7.2.1.文件的方法的对象

这一节中的示例的其余部分将承担已经创建了一个文件对象称为f

要读取文件内容,需要调用 f.read(size) ,该方法读取若干数量的数据并以字符串形式返回其内容size 是可选的数字参数size 被省略或者为负数时,将会读取并返回整个文件;如果文件大小是你机器内存的两倍时,这会产生问题。反之,则会以尽可能大的 size 读取和返回数据。如果到了文件末尾,f.read() 会返回一个空字符串("")。

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

f.readline()读取一行数据从文件 ;一个换行符 ( ) 留在字符串的末尾,并且如果该文件不会结束在换行符只省略在文件的最后一行。这使得返回值明确 ; f.readline()返回一个空字符串,如果文件的结尾已经达到,而代表一个空行''、 包含只有单一的换行符的字符串。

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

用于从文件中读取行,你可以遍历文件对象。这是内存高效、 快速,并导致简单的代码:

>>> for line in f:
        print line,
This is the first line of the file.
Second line of the file

如果您想要阅读的文件列表中的所有行你也可以使用list(f)f.readlines()

f.write(string)字符串的内容写入到该文件,返回

>>> f.write('This is a test\n')

要写点东西非字符串,它需要先转换为一个字符串:

>>> value = ('the answer', 42)
>>> s = str(value)
>>> f.write(s)

f.tell()返回一个整数,代表文件对象在文件中的指针位置,该数值计量了自文件开头到指针处的字节数。若要更改该文件对象的位置,请使用应对 (偏移量, from_what)新的位置由参考点加上 offset 计算得来,from_what参数选择的参考点。From_what值为 0 的措施从一开始的文件,1 使用当前的文件位置,并 2 使用文件的结尾作为参考点。from_what ,则可以省略,默认值为 0,使用该文件的开头作为参照点。

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

当你完成了一个文件时,调用f.close()将其关闭并释放任何打开的文件所占的系统资源。在调用f.close()后, 尝试使用文件对象将自动失败。

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

它是很好的做法处理文件对象时使用with关键字。这样的优点的文件是正确地关闭后其套件完成后,即使在路上引发的异常。它也是比编写等效-最后块短得多:

>>> with open('workfile', 'r') as f:
...     read_data = f.read()
>>> f.closed
True

文件对象有一些额外的方法,如isatty()truncate()并不经常使用的 ;有关文件对象的完整指南,请参阅库参考。

7.2.2.储蓄结构化数据与json

可以轻松地写入字符串,并为从文件中读取。数字采取更多努力,因为read ()方法仅返回字符串,将不得不将传递给一个函数int (),这需要像一个字符串喜欢'123'并返回其数值 123。当您想要保存更复杂的数据类型,如嵌套的列表和字典,解析和序列化用手变得复杂。

而不是让用户不断地编写和调试代码将复杂的数据类型保存到文件,Python 允许您使用常用数据交换格式称为JSON (JavaScript 对象符号)调用json的标准模块可以采取 Python 数据层次结构,并将它们转换为字符串表示形式 ;此过程称为序列化重构中的字符串表示形式的数据称为反序列化之间进行序列化和反序列化,表示该对象的字符串可能已存储在文件或数据,或通过网络连接到一些遥远的机器发送。

请注意

JSON 格式常用的现代应用程序中,以便进行数据交换。许多程序员都已经熟悉它,使它成为一个不错的选择的互操作性。

如果你有一个对象x,您可以查看其 JSON 字符串表示形式与简单的一行代码:

>>> json.dumps([1, 'simple', 'list'])
'[1, "simple", "list"]'

另一种变体的dumps()函数,称为dump(),只是序列化到一个文件对象。所以如果f是为写而打开一个文件对象,我们可以这样做:

json.dump(x, f)

再次,解码对象,如果f是一个文件对象已打开进行阅读:

x = json.load(f)

这种简单的序列化技术可以处理列表和字典,但序列化为 JSON 中的任意类实例需要一点额外的努力。 Json模块的引用包含对此作出解释。

请参阅

腌菜-腌菜模块

相反JSON泡菜是一项议定书,允许任意复杂的 Python 对象的序列化。为此,它是特定于 Python 和不能用来与其他语言编写的应用程序进行通信。它也是不安全,默认情况下: 反序列化来自不受信任源的泡菜数据可以执行任意代码,如果数据由熟练的攻击者精心设计。