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

7.1 更高级的输出格式

到目前为止,我们遇到了两种写值的方法:表达式语句print()函数。(第三种方法是使用write()文件对象的方法;标准输出文件可以引用为sys.stdout。有关此的更多信息,请参阅库参考。)

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

  • 要使用格式化的字符串文字,请在左引号或三引号之前f或之前开始字符串。在此字符串中,您可以在和字符F之间编写 Python 表达式, 这些字符可以引用变量或文字值。{}
>>>
>>> year = 2016
>>> event = 'Referendum'
>>> f'Results of the {year} {event}'
'Results of the 2016 Referendum'
  • 字符串的str.format()方法需要更多的人工操作。您仍将使用{and}标记将替换变量的位置,并且可以提供详细的格式化指令,但您还需要提供要格式化的信息。
    >>>
    >>> 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()orstr()函数将任何值转换为字符串。

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

一些例子:

>>> 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 格式化字符串文字

格式化字符串字面量(也简称为 f 字符串)允许您通过在字符串前面加上forF并将表达式写为 . 来将 Python 表达式的值包含在字符串中 {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'.
其他修饰符可用于在格式化之前转换值。 '!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'.

有关这些格式规范的参考,请参阅Format Specification Mini-Language的参考指南。

7.1.2  字符串格式()方法

该方法的基本用法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.

如果你有一个非常长的格式字符串,你不想拆分,那么如果你可以引用要按名称而不是位置格式化的变量,那就太好了。这可以通过简单地传递dict并使用方括号'[]'来访问键来完成

>>>
>>> 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()返回包含所有局部变量的字典。

例如,以下行生成一组整齐对齐的列,给出整数及其正方形和立方体:

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

(请注意,每列之间的一个空格是按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'

7.1.4 旧字符串格式

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

'string' % values%stringvalues

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

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

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

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

在文本模式下,读取时的默认设置是将特定于平台的行尾(\n在 Unix 上,\r\n在 Windows 上)转换为\n. 在文本模式下写入时,默认情况下会将出现的次数转换\n回特定于平台的行尾。这种对文件数据的幕后修改适用于文本文件,但会损坏文件中的二进制 JPEG数据EXE。在读写此类文件时要非常小心使用二进制模式。

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

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

如果你没有使用with关键字,那么你应该调用 f.close()关闭文件并立即释放它使用的任何系统资源。如果您没有明确关闭文件,Python 的垃圾收集器最终会销毁该对象并为您关闭打开的文件,但该文件可能会保持打开一段时间。另一个风险是不同的 Python 实现会在不同的时间进行这种清理。

在通过with语句或调用关闭文件对象后f.close(),尝试使用文件对象将自动失败。

>>>
>>> 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省略或为负时,将读取并返回文件的全部内容;如果文件是机器内存的两倍,那是你的问题。否则,最多读取并返回 size 个字符(在文本模式下)或 size 个字节(在二进制模式 )如果已到达文件末尾,f.read()将返回一个空字符串 ( '')。

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

f.readline()从文件中读取一行;换行符 ( \n) 留在字符串的末尾,如果文件不以换行符结尾,则仅在文件的最后一行省略。这使得返回值明确;如果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)将字符串的内容写入文件,返回写入的字符数。

>>>
>>> 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()返回一个整数,给出文件对象在文件中的当前位置,在二进制模式下表示为从文件开头开始的字节数,在文本模式下表示为不透明数字。

要更改文件对象的位置,请使用. 位置是通过将偏移量添加到参考点来计算的;参考点由wherece参数选择。whence值为 0从文件开头测量,1 使用当前文件位置,2 使用文件末尾作为参考点。 wherece可以省略,默认为 0,使用文件的开头作为参考点。f.seek(offset, whence)

>>>
>>> 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在模式字符串中没有 a 打开的文件),只允许相对于文件开头的查找(例外是查找以 结尾的文件)并且唯一有效的偏移值是从, 或零。任何其他偏移值都会产生未定义的行为。seek(0, 2)f.tell()

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

7.2.2. 保存结构化数据json

字符串可以很容易地从文件中写入和读取。数字需要更多的努力,因为该read()方法只返回字符串,必须将其传递给函数 like int(),该函数接受字符串 like'123' 并返回其数值 123。当您想要保存更复杂的数据类型时,例如嵌套列表和字典,手动解析和序列化变得复杂。

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

笔记:

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

如果你有一个 object x,你可以用一行简单的代码查看它的 JSON 字符串表示:

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

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

json.dump(x, f)

要再次解码对象,if f是一个已打开以供阅读的文本文件对象:

x = json.load(f)

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

pickle – 泡菜模块

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