7. 输入输出
7. 输入输出
7.1. 更复杂的输出格式
对输出格式的控制不只是打印空格分隔的值,还需要更多方式。格式化输出包括以下几种方法。
使用 格式化字符串字面值 ,要在字符串开头的引号/三引号前添加
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%'
请注意Notice how the
yes_votes
填充了空格并且只为负数添加了负号。
这个例子还打印了percentage
乘以 100 的结果,保留 2 个数位并带有一个百分号 (请参阅 格式规格迷你语言 了解详情)。最后,还可以用字符串切片和合并操作完成字符串处理操作,创建任何排版布局。字符串类型还支持将字符串按给定列宽进行填充,这些方法也很有用。
如果不需要花哨的输出,只想快速显示变量进行调试,可以用 repr()
或 str()
函数把值转化为字符串。
str()
函数返回供人阅读的值,repr()
则生成适于解释器读取的值(如果没有等效的语法,则强制执行 SyntaxError
)。
对于没有支持供人阅读展示结果的对象, str()
返回与 repr()
相同的值。一般情况下,数字、列表或字典等结构的值,使用这两个函数输出的表现形式是一样的。字符串有两种不同的表现形式。
示例如下:
s = 'Hello, world.'
str(s)
# 'Hello, world.'
repr(s)
# "'Hello, world.'"
str(1/7)
# '0.14285714285714285'
repr(1/7)
# '0.14285714285714285'
# The repr() of a string adds string quotes and backslashes:
hello = 'hello, world\n'
hellos = repr(hello)
print(hellos)
# 'hello, world\n'
x = 10 * 3.25
y = 200 * 200
# 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-字符串)在字符串前加前缀 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()
方法的对象。花括号中的数字表示传递给 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
zhaobc 注:因为 format
只有一个参数,而且是字典,所以 0[Jack]
代表第一个参数取 Jack
键。
而 0[Jack]:d
的 :d
代表使用十进制整数格式化。
这也可以通过将 table 字典作为采用 **
标记的关键字参数传入来实现。
table = {'Sjoerd': 4127, 'Jack': 4098, 'Dcab': 8637678}
print('Jack: {Jack:d}; Sojoerd: {Sojerd:d}; Dcab: {Dcab:d}'.format(**tab))
# Jack: 4098; Sjoerd: 4127; Dcab: 8637678
与内置函数 vars()
一同使用时这种方式非常实用,它将返回一个包含所有局部变量的字典:
vars()
# {'__name__': '__main__', '__doc__': None, '__package__': '_pyrepl', '__loader__': ... }
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__: _pyrepl; __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
str.format()
进行字符串格式化的完整概述详见 格式字符串语法 。
7.1.3. 手动格式化字符串
下面是使用手动格式化方式实现的同一个平方和立方的表:
for x in range(1, 11):
print(repr(x).rjust(2), repr(x*x).rjust(3), end=' ')
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()
返回一个 file object ,最常使用的是两个位置参数和一个关键字参数:open(filename, mode, encoding=None)
f = open('workfile', 'w', encoding="utf-8")
第一个实参是文件名字符串。
第二个实参是包含描述文件使用方式字符的字符串。
mode 的值包括 'r' ,表示文件只能读取;
'w' 表示只能写入(现有同名文件会被覆盖);
'a' 表示打开文件并追加内容,任何写入的数据会自动添加到文件末尾。
'r+' 表示打开文件进行读写。
mode 实参是可选的,省略时的默认值为 'r'。
通常情况下,文件是以 text mode 打开的,也就是说,你从文件中读写字符串,这些字符串是以特定的 encoding 编码的。
如果没有指定 encoding ,默认的是与平台有关的(见 open()
)。
在模式后面加上一个 'b' ,可以用 binary mode 打开文件。
二进制模式的数据是以 bytes 对象的形式读写的。在二进制模式下打开文件时,你不能指定 encoding 。
在文本模式下读取文件时,默认把平台特定的行结束符(Unix 上为 \n
, Windows 上为 \r\n
)转换为 \n
。
在文本模式下写入数据时,默认把 \n
转换回平台特定结束符。
这种操作方式在后台修改文件数据对文本文件来说没有问题,但会破坏 JPEG 或 EXE 等二进制文件中的数据。注意,在读写此类文件时,一定要使用二进制模式。
在处理文件对象时,最好使用 with
关键字。
优点是,子句体结束后,文件会正确关闭,即便触发异常也可以。
而且,使用 with
相比等效的 try
-finally
代码块要简短得多:
with open('workfile', encoding="utf-8") as f:
read_data = f.read()
# 们可以检测文件是否已被自动关闭。
f.closed
# True
如果没有使用 with
关键字,则应调用 f.close()
关闭文件,即可释放文件占用的系统资源。
注意
调用 f.write()
时,未使用 with
关键字,或未调用 f.close()
,即使程序正常退出,也 可能 导致 f.write()
的参数没有完全写入磁盘。
通过 with
语句,或调用 f.close()
关闭文件对象后,再次使用该文件对象将会失败。
7.2.1. 文件对象方法
f.read(size)
可用于读取文件内容,它会读取一些数据,并返回字符串(文本模式),或字节串对象(在二进制模式下)。size
是可选的数值参数。省略 size
或 size
为负数时,读取并返回整个文件的内容;文件大小是内存的两倍时,会出现问题。size
取其他值时,读取并返回最多 size
个字符(文本模式)或 size
个字节(二进制模式)。
如已到达文件末尾,f.read()
返回空字符串('')。
f.readline()
从文件中读取单行数据;字符串末尾保留换行符(\n
),只有在文件不以换行符结尾时,文件的最后一行才会省略换行符。
这种方式让返回值清晰明确;只要 f.readline()
返回空字符串,就表示已经到达了文件末尾,空行使用 '\n'
表示,该字符串只包含一个换行符。
从文件中读取多行时,可以用循环遍历整个文件对象。这种操作能高效利用内存,快速,且代码简单:
for line in f:
print(line, end='')
如需以列表形式读取文件中的所有行,可以用 list(f)
或 f.readlines()
。
f.write(string)
把 string 的内容写入文件,并返回写入的字符数。
写入其他类型的对象前,要先把它们转化为字符串(文本模式)或字节对象(二进制模式):
value = ('the answer', 42)
s = str(value) # # 将元组转换为字符串
# "('the answer', 42)"
f.write(s)
# 18
f.tell()
返回整数,给出文件对象在文件中的当前位置,表示为二进制模式下时从文件开始的字节数,以及文本模式下的意义不明的数字。
f.seek(offset, whence)
可以改变文件对象的位置。通过向参考点添加 offset
计算位置;参考点由 whence
参数指定。whence
值为 0 时,表示从文件开头计算,1 表示使用当前文件位置,2 表示使用文件末尾作为参考点。
省略 whence
时,其默认值为 0,即使用文件开头作为参考点。
f = open('workfile', 'rb+')
f.write(b'0123456789abcdef')
# 16
f.seek(5) # 定位到文件中的第 6 个字节
# b'5'
f.seek(-3, 2) # 定位到倒数第 3 个字节
# 13
f.read(1)
# b'd'
在文本文件(模式字符串未使用 b
时打开的文件)中,只允许相对于文件开头搜索(使用 seek(0, 2)
搜索到文件末尾是个例外),唯一有效的 offset
值是能从 f.tell()
中返回的,或 0。其他 offset 值都会产生未定义的行为。
文件对象还有一些额外的方法,如使用频率较低的 isatty()
和 truncate()
等;有关文件对象的完整指南请查阅标准库参考。
7.2.2. 使用 json 保存结构化数据
字符串可以很容易地写入文件或从文件中读取。当你想要保存嵌套列表和字典等更复杂的数据类型时,手动执行解析和序列化操作将会变得非常复杂。
只需一行简单的代码即可查看某个对象的 JSON 字符串表现形式:
import json
x = [1, 'simple', 'list']
json.dumps(x)
# '[1, "simple", "list"]'
dumps()
函数还有一个变体, dump()
,它只将对象序列化为 text file 。因此,如果 f 是 text file 对象,可以这样做:
json.dump(x, f)
要再次解码对象,如果 f 是已打开、供读取的 binary file 或 text file 对象:
x = json.load(f)
注
JSON文件必须以 UTF-8
编码。当打开JSON文件作为一个 text file 用于读写时,使用 encoding="utf-8"
。