7. Importing 输出

有几种方法可以显示程序的输出。数据可以以人类可读的形式打印,或写入文件以备将来使用。本章将讨论一些可能性。

7.1. 更高级的输出格式

到目前为止,我们已经遇到了两种编写值的方法:* expression statement *和print语句。 (第三种方法是使用文件对象的write()方法;可以将标准输出文件引用为sys.stdout.有关此信息,请参阅库参考.)

通常,您将需要对输出格式进行更多控制,而不是简单地打印以空格分隔的值。有两种格式化输出格式的方法;第一种方法是自己处理所有字符串;使用字符串切片和连接操作,您可以创建可以想象的任何布局。字符串类型有一些方法可以执行有用的操作,以将字符串填充到给定的列宽。这些将在稍后讨论。第二种方法是使用str.format()方法。

string模块包含Template类,该类提供了另一种将值替换为字符串的方法。

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

str()函数用于返回相当容易理解的值的表示形式,而repr()用于生成可以由解释器读取的表示形式(如果没有等效语法,则强制使用SyntaxError)。对于没有特定代表人类消费的对象,str()将返回与repr()相同的值。许多值(例如数字或结构,如列表和字典)使用任一函数都具有相同的表示形式。字符串和浮点数尤其具有两种不同的表示形式。

Some examples:

>>> 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()方法,该方法pass在左侧使用空格填充来在给定宽度的字段中对字符串进行右对齐。有类似的方法str.ljust()str.center()。这些方法不写任何东西,它们只是返回一个新字符串。如果 Importing 字符串太长,则它们不会截断它,而是将其保持不变。这会弄乱您的列布局,但通常比替代方法要好得多,后者将覆盖一个值。 (如果您确实想要截断,则可以始终像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

如果您不想拆分很长的格式字符串,则最好引用要按名称而不是按位置格式化的变量。这可以pass简单地传递字典并使用方括号'[]'来访问密钥来完成

>>> 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

也可以pass将表作为带有“ **”符号的关键字参数传递来完成。

>>> 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()返回文件对象,最常与两个参数一起使用:open(filename, mode)

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

第一个参数是包含文件名的字符串。第二个参数是另一个包含一些字符的字符串,这些字符描述了文件的使用方式。仅读取文件时,* mode *可以为'r',仅写入时为'w'(将删除具有相同名称的现有文件),并且'a'打开文件以进行附加;写入文件的所有数据都会自动添加到末尾。 'r+'打开文件以供读取和写入。 * mode *参数是可选的;如果Ellipsis了'r'

在 Windows 上,附加到该模式的'b'以二进制模式打开文件,因此也有'rb''wb''r+b'之类的模式。 Windows 上的 Python 区分文本文件和二进制文件。当读取或写入数据时,文本文件中的行尾字符会自动更改。对于 ASCII 文本文件来说,对文件数据进行这种幕后修改是可以的,但是它会破坏JPEGEXE文件中的二进制数据。读写此类文件时,请务必小心使用二进制模式。在 Unix 上,将'b'附加到该模式并没有什么坏处,因此您可以独立于平台使用它来处理所有二进制文件。

7.2.1. 文件对象的方法

本节的其余示例将假定已创建名为f的文件对象。

要读取文件的内容,请调用f.read(size),它读取一些数据并将其作为字符串返回。 * size 是可选的数字参数。如果 size 被Ellipsis或为负,则将读取并返回文件的全部内容;如果文件的大小是计算机内存的两倍,那是您的问题。否则,最多读取并返回 size *个字节。如果已到达文件末尾,则f.read()将返回一个空字符串("")。

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

f.readline()从文件中读取一行;换行符(\n)留在字符串的末尾,并且如果文件未以换行符结尾,则仅在文件的最后一行Ellipsis。这使得返回值明确。如果f.readline()返回一个空字符串,则说明已到达文件末尾,而空行由'\n'表示,该字符串仅包含一个换行符。

>>> 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)将* string *的内容写入文件,返回None

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

要编写字符串以外的内容,首先需要将其转换为字符串:

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

f.tell()返回一个整数,给出文件对象在文件中的当前位置,以从文件开头开始的字节数为单位。要更改文件对象的位置,请使用f.seek(offset, from_what)。位置是pass将* offset 与参考点相加来计算的;参考点由 from_what *参数选择。 * from_what *值为 0,从文件开头开始测量,1 使用当前文件位置,2 使用文件结尾作为参考点。 * from_what *可以Ellipsis,默认为 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()后,try使用文件对象将自动失败。

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

在处理文件对象时,最好使用with关键字。这样做的好处是,即使在执行过程中引发了异常,文件在其套件完成后也将正确关闭。它也比编写等效的try-finally块短得多:

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

文件对象还有一些其他方法,例如isatty()truncate(),这些方法不太常用。有关文件对象的完整指南,请参考 Library 参考。

7.2.2. 使用 JSON 保存结构化数据

字符串可以轻松地写入文件和从文件读取。数字需要花费更多的精力,因为read()方法仅返回字符串,必须将其传递给类似int()的函数,该函数需要像'123'这样的字符串并返回其数字值 123.当您要保存更复杂的数据类型时,例如嵌套列表和字典,手工解析和序列化变得很复杂。

Python 允许用户使用流行的数据交换格式JSON(JavaScript 对象表示法),而不是让用户不断编写和调试代码以将复杂的数据类型保存到文件中。名为json的标准模块可以获取 Python 数据层次结构,并将其转换为字符串表示形式。此过程称为序列化。从字符串表示形式重构数据称为* deserializing *。在序列化和反序列化之间,代表对象的字符串可能已存储在文件或数据中,或者已pass网络连接发送到某个远程机器。

Note

JSON 格式被现代应用程序普遍使用以允许数据交换。许多程序员已经熟悉它,这使其成为互操作性的不错选择。

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

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

dumps()函数的另一个变体dump()只是将对象序列化为文件。因此,如果f是要写入的file object,我们可以这样做:

json.dump(x, f)

要重新解码该对象,如果f是已打开以供读取的file object

x = json.load(f)

这种简单的序列化技术可以处理列表和字典,但是序列化 JSON 中的任意类实例需要一些额外的工作。 json模块的参考包含对此的说明。

See also

pickle-pickle 模块

JSON相反,* pickle *是一种协议,该协议允许序列化任意复杂的 Python 对象。因此,它特定于 Python,不能用于与以其他语言编写的应用程序进行通信。默认情况下,它也是不安全的:反序列化来自不可信来源的 pickle 数据,如果该数据是由熟练的攻击者制作的,则可以执行任意代码。