Python教程第六章 输入输出
本系列文章是我学习Python3.9的官方tutorial的笔记,大部分来源于官网的中文翻译,但由于该翻译有些部分实在太差和啰嗦,我做了很多删除和修改,还有部分原文讲不明白的,我参考其他资料增加了进一步阐述说明。
6.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
>>> 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()
是用于生成解释器可读的表示,如果没有人类可读性的表示,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'))"
6.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
注意,{phone:10d}
中的d
代码整数以10进制输出,更多的规范请参考格式规格迷你语言。
其他的修饰符可用于在格式化之前转化值。 '!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'.
有关这些同样参考 格式规格迷你语言。
6.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
这也可以通过使用 '**' 符号将 table 作为关键字参数传递。
>>> 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
6.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()
的end参数添加的)
字符串对象的 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'
6.1.4 旧的字符串格式化方法
% 运算符也可用于字符串格式化。 给定 '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 风格的字符串格式化 部分找到更多信息。
6.2 读写文件
open()
返回一个 file object,最常用的有两个参数: open(filename, mode)
。
>>> f = open('workfile', 'w')
第一个参数是包含文件名的字符串。第二个参数是另一个字符串,其中包含一些描述文件使用方式的字符。mode 可以是 'r' ,表示文件只能读取,'w' 表示只能写入(已存在的同名文件会被删除),还有 'a' 表示打开文件以追加内容;任何写入的数据会自动添加到文件的末尾。'r+' 表示打开文件进行读写。mode 参数是可选的;省略时默认为 'r'。
通常文件是以 text mode 打开的,这意味着从文件中读取或写入字符串时,都会以指定的编码方式进行编码。如果未指定编码格式,默认值与平台相关 (参见 open()
)。在mode 中追加的 'b'
则以 binary mode 打开文件。
在文本模式下读取时,默认会把平台特定的行结束符 (Unix 上的 \n
, Windows 上的 \r\n
) 转换为 \n
。在文本模式下写入时,默认会把出现的 \n
转换回平台特定的结束符。这样在幕后修改文件数据对文本文件来说没有问题,但是会破坏二进制数据例如 JPEG 或 EXE 文件中的数据。请一定要注意在读写此类文件时应使用二进制模式。
在处理文件对象时,最好使用 with
关键字。 优点是当子句体结束后文件会正确关闭,即使在某个时刻引发了异常。 而且使用 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
如果你没有使用 with
关键字,那么你应该调用 f.close()
来关闭文件并立即释放它使用的所有系统资源。如果你没有显式地关闭文件,Python的垃圾回收器最终将销毁该对象并为你关闭打开的文件,但这个文件可能会保持打开状态一段时间。另外一个风险是不同的Python实现会在不同的时间进行清理。
6.2.1 文件对象的方法
要读取文件内容,可以调用 f.read(size),它会读取一些数据并将其作为字符串(在文本模式下)或字节串对象(在二进制模式下)返回。 size 是一个可选的数值参数。 当 size 被省略或者为负数时,将读取并返回整个文件的内容;如果文件的大小是你的机器内存的两倍就会出现问题。 当取其他值时,将读取并返回至多 size 个字符(在文本模式下)或 size 个字节(在二进制模式下)。 如果已到达文件末尾,f.read() 将返回一个空字符串 ('')。
>>> f.read()
'This is the entire file.\n'
>>> f.read()
''
要从文件中读取行,你可以循环遍历文件对象。这是内存高效,快速的,并简化代码:
>>> 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 参数指定。 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'
在文本文件中,只允许相对于文件开头搜索(使用 seek(0, 2) 搜索到文件末尾是个例外)并且唯一有效的 offset 值是那些能从 f.tell() 中返回的或者是零。其他 offset 值都会产生未定义的行为。
6.2.2 使用json保存结构化数据
如果你有一个对象 x ,你可以用一行简单的代码来查看它的 JSON 字符串表示:
>>> import json
>>> json.dumps([1, 'simple', 'list'])
'[1, "simple", "list"]'
dumps()
函数的另一个变体叫做 dump()
,它只是将对象序列化为文件。因此,如果 f
是一个 text file 对象,我们可以这样做:
json.dump(x, f)
要再次解码对象,如果 f
是一个打开的以供阅读的 text file 对象:
x = json.load(f)