7. Importing 输出

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

7.1. 更高级的输出格式

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

通常,您将需要对输出格式进行更多控制,而不是简单地打印以空格分隔的值。有几种格式化输出的方法。

  • 要使用格式化的字符串 Literals,请在开头的引号或三引号之前以fF开头的字符串。在此字符串内,您可以在{}字符之间编写一个 Python 表达式,该表达式可以引用变量或 Literals 值。
>>> year = 2016
>>> event = 'Referendum'
>>> f'Results of the {year} {event}'
'Results of the 2016 Referendum'
  • 字符串的str.format()方法需要更多的人工。您仍将使用{}来标记变量将被替换的位置,并可以提供详细的格式化指令,但是您还需要提供要格式化的信息。
>>> yes_votes = 42_572_654
>>> no_votes = 43_132_495
>>> percentage = yes_votes / (yes_votes + no_votes)
>>> '{:-9} YES votes  {:2.2%}'.format(yes_votes, percentage)
' 42572654 YES votes  49.67%'
  • 最后,您可以使用字符串切片和连接操作来创建您可以想象的任何布局,从而完成所有字符串处理。字符串类型有一些方法可以执行有用的操作,以将字符串填充到给定的列宽。

当您不需要花哨的输出而只想快速显示一些变量以进行调试时,可以使用repr()str()函数将任何值转换为字符串。

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

Some examples:

>>> s = 'Hello, world.'
>>> str(s)
'Hello, world.'
>>> repr(s)
"'Hello, world.'"
>>> str(1/7)
'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'))"

string模块包含一个Template类,该类提供了另一种将值替换为字符串的方式,使用了诸如$x之类的占位符并将其替换为字典中的值,但是对格式的控制要少得多。

7.1.1. 格式化字符串 Literals

格式化的字符串 Literals(简称为 f 字符串)可让您在字符串中包含 python 表达式的值,方法是在字符串前面加上fF作为前缀并将表达式写为{expression}

表达式后面可以有一个可选的格式说明符。这样可以更好地控制值的格式。以下示例将 pi 舍入到小数点后三位:

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

':'之后传递整数将导致该字段的最小字符宽度。这对于使列对齐很有用。

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

其他修饰符可用于在格式化之前转换该值。 '!a'适用ascii()'!s'适用str(),并且'!r'适用repr()

>>> animals = 'eels'
>>> print(f'My hovercraft is full of {animals}.')
My hovercraft is full of eels.
>>> print(f'My hovercraft is full of {animals!r}.')
My hovercraft is full of 'eels'.

有关这些格式规范的参考,请参见格式规格迷你语言的参考指南。

7.1.2. 字符串 format()方法

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.

如果您不想拆分很长的格式字符串,那么最好引用要按名称而不是按位置格式化的变量。这可以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()结合使用时,此函数特别有用,该函数返回包含所有局部变量的字典。

例如,以下几行产生一组整齐对齐的列,这些列给出整数及其平方和立方:

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

有关使用str.format()进行字符串格式化的完整概述,请参见格式字符串语法

7.1.3. 手动字符串格式

这是相同的正方形和立方体表,手动设置格式:

>>> for x in range(1, 11):
...     print(repr(x).rjust(2), repr(x*x).rjust(3), end=' ')
...     # Note use of 'end' 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

(请注意,每一列之间的空格是passprint()的方式添加的:它总是在其参数之间添加空格。)

字符串对象的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'

7.1.4. 旧字符串格式

%运算符(模)也可以用于字符串格式化。给定'string' % values,用values的零个或多个元素替换string中的%实例。此操作通常称为字符串插值。例如:

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

可以在printf 样式的字符串格式部分中找到更多信息。

7.2. 读写文件

open()返回file object,最常与两个参数一起使用:open(filename, mode)

>>> f = open('workfile', 'w')

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

通常,文件以文本模式打开,这意味着您在文件中读取和写入字符串,这些字符串以特定的编码进行编码。如果未指定编码,则默认值取决于平台(请参见open())。附加到该模式的'b'以“二进制模式”打开文件:现在,数据以字节对象的形式被读写。此模式应用于所有不包含文本的文件。

在文本模式下,读取时的默认值是将特定于平台的行尾(在 Unix 上为\n,在 Windows 上为\r\n)转换为\n。在文本模式下编写时,默认设置是将\n的出现转换回特定于平台的行结尾。这种对文件数据的幕后修改对于文本文件而言是很好的选择,但会破坏JPEGEXE文件中的二进制数据。读写此类文件时,请务必小心使用二进制模式。

在处理文件对象时,最好使用with关键字。这样做的好处是,即使在某个时候引发了异常,该文件在其套件完成后也会正确关闭。使用with也比编写等效的try-finally块要短得多:

>>> with open('workfile') as f:
...     read_data = f.read()

>>> # We can check that the file has been automatically closed.
>>> f.closed
True

如果您未使用with关键字,则应调用f.close()关闭文件并立即释放文件使用的所有系统资源。如果您未明确关闭文件,Python 的垃圾回收器finally将破坏该对象并为您关闭打开的文件,但该文件可能会保持打开状态一段时间。另一个风险是,不同的 Python 实现将在不同的时间进行清理。

passwith语句或调用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.

7.2.1. 文件对象的方法

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

要读取文件的内容,请调用f.read(size),它读取一些数据并将其作为字符串(在文本模式下)或字节对象(在二进制模式下)返回。 * size 是可选的数字参数。如果 size 被Ellipsis或为负,则将读取并返回文件的全部内容;如果文件的大小是计算机内存的两倍,那是您的问题。否则,最多读取并返回 size 个字符(在文本模式下)或 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, end='')
...
This is the first line of the file.
Second line of the file

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

f.write(string)将* string *的内容写入文件,并返回写入的字符数。

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

写入对象之前,需要将其他类型的对象转换为字符串(在文本模式下)或字节对象(在二进制模式下):

>>> value = ('the answer', 42)
>>> s = str(value)  # convert the tuple to string
>>> f.write(s)
18

f.tell()返回一个整数,该整数给出二进制文件模式下文件对象在文件中的当前位置,表示为从文件开头的字节数,而在文本模式下为不透明数字。

要更改文件对象的位置,请使用f.seek(offset, whence)。位置是pass将* offset 与参考点相加来计算的;参考点由 whence *参数选择。 * whence *值为 0(从文件开头开始测量),1 使用当前文件位置,2 使用文件结尾作为参考点。 * whence *可以Ellipsis,默认为 0,使用文件的开头作为参考点。

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

在文本文件中(那些在模式字符串中打开而没有b的文件),仅允许相对于文件开头的查找(exception 是查找到以seek(0, 2)结尾的文件),唯一有效的* offset 值是返回的值从f.tell()或零开始。任何其他 offset *值都会产生不确定的行为。

文件对象还有一些其他方法,例如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()只是将对象序列化为text file。因此,如果f是为写入而打开的text file对象,我们可以这样做:

json.dump(x, f)

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

x = json.load(f)

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

See also

pickle-pickle 模块

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