7. 输入和输出 — Python 文档

来自菜鸟教程
Python/docs/3.6/tutorial/inputoutput
跳转至:导航、​搜索

7. 输入和输出

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

7.1. 更高级的输出格式

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

通常,与简单地打印空格分隔值相比,您需要更多地控制输出的格式。 有两种方法可以格式化输出; 第一种方法是自己处理所有字符串; 使用字符串切片和连接操作,您可以创建您可以想象的任何布局。 string 类型有一些方法可以执行有用的操作来将字符串填充到给定的列宽; 这些将很快讨论。 第二种方法是使用 格式化字符串文字 ,或 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/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'))"

这里有两种写正方形和立方体表格的方法:

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

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

'!a'(应用 ascii())、'!s'(应用 str())和 '!r'(应用 repr ()) 可用于在格式化之前转换值:

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

可选的 ':' 和格式说明符可以跟在字段名称之后。 这允许更好地控制值的格式。 以下示例将 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.

更多信息可以在 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 转换回特定于平台的行尾。 这种对文件数据的幕后修改适用于文本文件,但会破坏 JPEGEXE 文件中的二进制数据。 读写此类文件时要非常小心地使用二进制模式。

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

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

如果您没有使用 关键字,那么您应该调用 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 个字节。 如果已到达文件末尾,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, from_what)。 位置是通过将 offset 添加到参考点来计算的; 参考点由 from_what 参数选择。 from_what 值 0 从文件开头开始测量,1 使用当前文件位置,2 使用文件结尾作为参考点。 from_what 可以省略,默认为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 模块的参考包含对此的解释。

也可以看看

pickle - pickle 模块

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