7. 输入和输出 — Python 文档
7. 输入和输出
有几种方法可以显示程序的输出; 数据可以以人类可读的形式打印,或写入文件以备将来使用。 本章将讨论一些可能性。
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() 方法需要更多的手动操作。 您仍将使用
{
和}
来标记变量将被替换的位置并可以提供详细的格式化指令,但您还需要提供要格式化的信息。>>> 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%'
最后,您可以通过使用字符串切片和连接操作来创建您可以想象的任何布局,从而自己完成所有字符串处理。 string 类型有一些方法可以执行有用的操作来将字符串填充到给定的列宽。
当您不需要花哨的输出而只想快速显示一些变量用于调试时,您可以使用 repr() 或 str() 将任何值转换为字符串职能。
str() 函数旨在返回相当人类可读的值的表示,而 repr() 旨在生成可由解释器读取的表示(或将如果没有等效的语法,则强制 SyntaxError)。 对于没有特定表示供人类消费的对象,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. 格式化字符串文字
Formatted string literals(也简称为 f-strings)让你在字符串中包含 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'.
有关这些格式规范的参考,请参阅 格式规范迷你语言 的参考指南。
7.1.2. String 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.
如果您有一个不想拆分的非常长的格式字符串,那么如果您可以引用要按名称而不是按位置格式化的变量,那就太好了。 这可以通过简单地传递字典并使用方括号 '[]'
来访问键来完成。
>>> 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. 旧的字符串格式
% o 运算符(模)也可用于字符串格式化。 给定 'string' % values
,string
中 %
的实例被替换为 values
的零个或多个元素。 此操作通常称为字符串插值。 例如:
>>> import math
>>> print('The value of pi is approximately %5.3f.' % math.pi)
The value of pi is approximately 3.142.
更多信息可以在 printf-style String Formatting 部分找到。
7.2. 读写文件
open() 返回一个 文件对象 ,最常用的有两个参数:open(filename, mode)
。
>>> f = open('workfile', 'w')
第一个参数是一个包含文件名的字符串。 第二个参数是另一个字符串,其中包含一些描述文件使用方式的字符。 mode可以是'r'
只读取文件,'w'
只写入(已存在的同名文件将被删除),[ X153X] 打开文件进行追加; 写入文件的任何数据都会自动添加到末尾。 'r+'
打开文件进行读写。 mode 参数是可选的; 'r'
。
通常,文件以 文本模式 打开,这意味着您可以从文件中读取和向文件写入字符串,这些字符串以特定编码进行编码。 如果未指定编码,则默认值取决于平台(请参阅 open())。 'b'
附加到模式以二进制模式打开文件:现在以字节对象的形式读取和写入数据。 此模式应用于所有不包含文本的文件。
在文本模式下,读取时的默认值是将特定于平台的行结尾(Unix 上的 \n
,Windows 上的 \r\n
)转换为 \n
。 在文本模式下写入时,默认情况下将出现的 \n
转换回特定于平台的行尾。 这种对文件数据的幕后修改适用于文本文件,但会破坏 JPEG
或 EXE
文件中的二进制数据。 读写此类文件时要非常小心地使用二进制模式。
在处理文件对象时,最好使用 和 关键字。 优点是文件在其套件完成后会正确关闭,即使在某些时候引发异常。 使用 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
如果您没有使用 和 关键字,那么您应该调用 f.close()
来关闭文件并立即释放它使用的所有系统资源。
警告
在不使用 with
关键字的情况下调用 f.write()
或调用 f.close()
可能会导致 f.write()
的参数没有完全写入磁盘,即使程序成功退出。
在关闭文件对象后,无论是通过 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)
将 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)
。 位置是通过将 offset 添加到参考点来计算的; 参考点由 whence 参数选择。 A whence 值为 0 从文件开头开始测量,1 使用当前文件位置,2 使用文件结尾作为参考点。 whence 可以省略,默认为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
的情况下打开的文件),只允许相对于文件开头的搜索(例外是搜索到带有 seek(0, 2)
的文件结尾)和唯一有效的 offset 值是从 f.tell()
返回的值,或者为零。 任何其他 offset 值都会产生未定义的行为。
文件对象有一些额外的方法,例如 isatty()
和 truncate()
不太常用; 有关文件对象的完整指南,请参阅库参考。
7.2.2. 保存结构化数据 json
字符串可以很容易地写入文件和从文件中读取。 数字需要更多的努力,因为 read()
方法只返回字符串,必须将其传递给像 int() 这样的函数,该函数接受像 '123'
这样的字符串] 并返回其数值 123。 当您想要保存更复杂的数据类型(如嵌套列表和字典)时,手动解析和序列化变得复杂。
Python 允许您使用流行的数据交换格式 JSON(JavaScript Object Notation),而不是让用户不断编写和调试代码来将复杂的数据类型保存到文件中。 名为 json 的标准模块可以采用 Python 数据层次结构,并将它们转换为字符串表示; 这个过程称为序列化。 从字符串表示中重建数据称为 反序列化 。 在序列化和反序列化之间,表示对象的字符串可能已存储在文件或数据中,或者通过网络连接发送到某个远程机器。
笔记
现代应用程序通常使用 JSON 格式来进行数据交换。 许多程序员已经熟悉它,这使其成为互操作性的不错选择。
如果你有一个对象 x
,你可以用一行简单的代码查看它的 JSON 字符串表示:
>>> import json
>>> json.dumps([1, 'simple', 'list'])
'[1, "simple", "list"]'
dumps() 函数的另一个变体,称为 dump(),简单地将对象序列化为 文本文件 。 因此,如果 f
是一个为写入而打开的 文本文件 对象,我们可以这样做:
json.dump(x, f)
再次解码对象,如果 f
是一个已打开读取的 文本文件 对象:
x = json.load(f)
这种简单的序列化技术可以处理列表和字典,但在 JSON 中序列化任意类实例需要一些额外的努力。 json 模块的参考包含对此的解释。