输入与输出

程序输出有几种显示方式;数据既可以输出供人阅读的形式,也可以写入文件备用。本章探讨一些可用的方式。

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
>>> total_votes = 85_705_149
>>> percentage = yes_votes / total_votes
>>> '{:-9} YES votes  {:2.2%}'.format(yes_votes, percentage)
' 42572654 YES votes  49.67%'

请注意,yes_votes 填充了空格,并且只为负数添加负号。这个例子还打印了 percentage 乘以 100 的结果,保留两位小数并带有百分号。细节见格式说明迷你语言。

  • 最后,还可以用字符串切片和拼接操作完成字符串处理,创建任意排版布局。字符串类型也支持将字符串按给定列宽填充。

如果不需要花哨的输出,只想快速显示变量进行调试,可以用 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 模块通过 string.Template 提供基于正则表达式的简单模板方法。它使用类似 $x 的占位符,并用字典中的对应值替换。这种语法容易使用,但格式控制能力有限。

7.1.1. 格式化字符串字面值

格式化字符串字面值(简称 f 字符串)在字符串前加上 f 或 F,通过 {expression} 将 Python 表达式的值包含在字符串中。

可选的格式说明符写在表达式后面,以更精细地控制值的格式。下例将 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'.

= 说明符可以把表达式扩展为表达式文本、等号和求值结果的表示。

>>> bugs = 'roaches'
>>> count = 13
>>> area = 'living room'
>>> print(f'Debugging {bugs=} {count=} {area=}')
Debugging bugs='roaches' count=13 area='living room'

有关 = 的更多信息,见自说明表达式;格式说明的参考见格式说明迷你语言。

7.1.2. 字符串 format() 方法

str.format() 的基本用法如下:

>>> print('We are the {} who say "{}!"'.format('knights', 'Ni'))
We are the knights who say "Ni!"

花括号和其中的字符称为格式字段,会被传入 str.format() 的对象替换。花括号中的数字可引用参数的位置。

>>> print('{0} and {1}'.format('spam', 'eggs'))
spam and eggs
>>> print('{1} and {0}'.format('spam', 'eggs'))
eggs and spam

如果使用关键字参数,可以通过参数名引用其值。

>>> 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() 结合时,这种方法尤其有用:

>>> table = {k: str(v) for k, v in vars().items()}
>>> message = " ".join([f'{k}: ' + '{' + k +'};' for k in table.keys()])
>>> print(message.format(**table))
__name__: __main__; __doc__: None; __package__: None; __loader__: ...

以下代码会生成整齐对齐的列,显示整数及其平方和立方:

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

完整概述见格式字符串语法。

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. 旧式字符串格式化方法

% 运算符(求余)也可用于字符串格式化。对于 format % values(format 是字符串),format 中的 % 转换说明符会由 values 中的零个或多个元素替换。这通常称为字符串插值。

>>> 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, encoding=None)。

>>> f = open('workfile', 'w', encoding="utf-8")

第一个实参是文件名字符串。第二个实参是由几个字符组成的字符串,用来描述文件的使用方式。r 表示只读;w 表示只写(已有同名文件会被清空);a 表示追加,写入的数据自动添加到文件末尾;r+ 表示读写。mode 可省略,默认是 r。

文件通常以文本模式打开:读写的是采用指定编码的字符串。如果未指定 encoding,默认编码取决于平台。UTF-8 是现代事实上的标准,因此除非明确需要其他编码,建议使用 encoding="utf-8"。在模式中添加 b 会以二进制模式打开文件;数据作为 bytes 对象读写。二进制模式不能指定 encoding。

文本模式读取时,默认将平台特定的行结束符(Unix 的 \n、Windows 的 \r\n)转换为 \n;写入时默认将 \n 转换回平台特定的行结束符。这种后台修改对文本文件适用,但会破坏 JPEG 或 EXE 等二进制文件的数据。读写此类文件务必使用二进制模式。

处理文件对象时,最好使用 with。它的优点是代码块结束后文件会正确关闭,即便其中某处引发异常。它也比等效的 try—finally 代码更简短。

>>> with open('workfile', encoding="utf-8") as f:
...     read_data = f.read()

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

没有使用 with 时,应调用 f.close() 关闭文件并立即释放系统资源。

警告:调用 f.write() 时未使用 with 或未调用 f.close(),即使程序成功退出,也可能导致写入内容未完全写到磁盘。

文件对象被 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) 读取一定数量的数据,返回字符串(文本模式)或 bytes 对象(二进制模式)。size 是可选的数值参数。省略 size 或其值为负时,会读取并返回全部内容;如果文件比机器内存大一倍,内存问题需要你自行承担。否则,最多读取并返回 size 个字符(文本模式)或字节(二进制模式)。到达文件末尾时,f.read() 返回空字符串 ''。

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

f.readline() 读取一行。字符串末尾保留换行符 \n;只有文件不以换行符结尾时,最后一行才省略它。因此返回值不会混淆:空字符串表示到达文件末尾;空行则是仅含换行符的字符串 '\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

其他类型的对象必须先转换成字符串(文本模式)或 bytes 对象(二进制模式)再写入:

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

f.tell() 返回一个整数,表示文件对象的当前位置。在二进制模式中,它是距文件开头的字节数;在文本模式中,它是不透明的数值。

f.seek(offset, whence) 改变文件位置。新位置由参考点加上 offset 得到。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() 返回的值或 0;其他值产生未定义行为。

文件对象还有 isatty() 和 truncate() 等使用较少的方法。完整指南见标准库参考。

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

字符串容易写入文件或从中读取。数字稍麻烦,因为 read() 只返回字符串,需要传给 int() 等函数,将 '123' 转换为数值 123。保存嵌套列表和字典等复杂数据时,手动解析和序列化会变得复杂。

Python 支持流行的数据交换格式 JSON(JavaScript Object Notation),让用户无需不断编写和调试复杂数据的保存代码。标准库 json 模块将有层级结构的 Python 数据转换成字符串表示,这称为序列化;从字符串表示重建数据称为反序列化。在两者之间,代表对象的字符串可以保存到文件或数据库,也可以经网络发往远端机器。

备注:现代应用常使用 JSON 交换数据,许多程序员已经熟悉它,因此它很适合实现互操作。

只需一行代码,就能查看对象的 JSON 字符串表示:

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

dumps() 的变体 dump() 将对象直接序列化到文本文件。如果 f 是以写入模式打开的文本文件对象,可这样做:

json.dump(x, f)

要再次解码对象,若 f 是为读取而打开的二进制或文本文件对象:

x = json.load(f)

备注:JSON 文件必须采用 UTF-8 编码。以文本文件方式读写 JSON 时,使用 encoding="utf-8"。

这种简单的序列化技术可以处理列表和字典,但序列化任意类的实例需要额外工作。json 模块参考解释了相关做法。

参见:pickle 模块。与 JSON 不同,pickle 允许序列化任意复杂的 Python 对象。它为 Python 专用,无法与其他语言编写的应用通信。默认情况下它也不安全:反序列化不可信来源的 pickle 数据,可能执行攻击者精心构造的任意代码。

来源与许可

来源:Python Software Foundation 与文档、翻译贡献者。英文 Canonical 章节;官方中文章节。核对日期:2026-10-03;所见页面版本:Python 3.14.8 文档。Copyright © 2001 Python Software Foundation; All Rights Reserved.

正文依据官方中文章节并对照英文,完整保留本章主题、示例和警告;调整部分措辞及链接,代码和示例输出保留英文 Canonical 内容。代码中的注释保持英文。未执行示例,示例输出来自原文。

正文采用 Python Software Foundation License Version 2;文档示例、配方与其他代码另采用 Zero Clause BSD License。下附适用的 PSF v2 完整许可:

PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2

1. This LICENSE AGREEMENT is between the Python Software Foundation ("PSF"), and
   the Individual or Organization ("Licensee") accessing and otherwise using this
   software ("Python") in source or binary form and its associated documentation.

2. Subject to the terms and conditions of this License Agreement, PSF hereby
   grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce,
   analyze, test, perform and/or display publicly, prepare derivative works,
   distribute, and otherwise use Python alone or in any derivative
   version, provided, however, that PSF's License Agreement and PSF's notice of
   copyright, i.e., "Copyright © 2001 Python Software Foundation; All Rights
   Reserved" are retained in Python alone or in any derivative version
   prepared by Licensee.

3. In the event Licensee prepares a derivative work that is based on or
   incorporates Python or any part thereof, and wants to make the
   derivative work available to others as provided herein, then Licensee hereby
   agrees to include in any such work a brief summary of the changes made to Python.

4. PSF is making Python available to Licensee on an "AS IS" basis.
   PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED.  BY WAY OF
   EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND DISCLAIMS ANY REPRESENTATION OR
   WARRANTY OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE
   USE OF PYTHON WILL NOT INFRINGE ANY THIRD PARTY RIGHTS.

5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
   FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF
   MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, OR ANY DERIVATIVE
   THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.

6. This License Agreement will automatically terminate upon a material breach of
   its terms and conditions.

7. Nothing in this License Agreement shall be deemed to create any relationship
   of agency, partnership, or joint venture between PSF and Licensee.  This License
   Agreement does not grant permission to use PSF trademarks or trade name in a
   trademark sense to endorse or promote products or services of Licensee, or any
   third party.

8. By copying, installing or otherwise using Python, Licensee agrees
   to be bound by the terms and conditions of this License Agreement.
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容