查看全部 Python3 基础教程
高级格式化输出
目前说了两种写值的方式:表达式语句和 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%'
-
最后,可以通过使用字符串切片和连接操作来自己处理所有的字符串,创建想要的任何布局。字符串类型有一些方法可以将字符串填充到给定列宽度。
当不需要高级输出而只需要快速显示某些变量以进行调试时,可以使用 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
这样的占位符,并用字典中的值替换它们,但对格式的控制要少得多。
格式化字符串
格式化字符串,简称为 f-strings,是将 Python 表达式的值放入以 f
或 F
开头的字符串中,表达式写为 {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'.
关于这些格式化规范,可以参考 Format Specification Mini-Language。
字符串 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()
进行格式化字符串的完整用法,可以参考 Format String Syntax。
手动格式化字符串
手动格式化来生成同样的平方、立方数表。
>>> 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'
旧的字符串格式化方式
取模操作符 %
也可以用于字符串格式化。给定 '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 部分。
读写文件
open() 函数返回一个文件对象,经常带两个参数 open(filename, mode)
。
>>> f = open('workfile', 'w')
第一个参数是一个包含文件名的字符串,第二个参数 mode
是另一个用于描述如何使用该文件的字符串。
mode
参数有以下几种:
- 设置
mode
为r
只能读取文件; - 设置
mode
为w
只能写入文件,如果已经存在一个同名的文件,则该文件内容会被抹去; - 设置
mode
为a
只能往文件末尾追加内容; - 设置
mode
为r+
既可以读取文件,也可以写入文件。 - 附加
b
到mode
会以二进制模式打开文件,然后以字节对象的形式对文件进行读写操作(rb+
),该方式用于非文本文件。
mode
参数是可选的,如果省略,默认使用r
。
通常会以文本模式打开文件,这意味着是以字符串对特定编码的文件进行读写操作。如果未指定编码,则默认编码依平台而定(参考open()
)。
在文本模式中,读取时默认会将特定平台的换行符(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()
关闭文件,立即释放其使用的任何系统资源。
注意,不使用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.
文件对象方法
本节中的以下示例假设 f
是一个已经创建的文件对象。
读取文件内容用 f.read(size)
方法,该方法会读取一定数量的数据,并在文本模式下以字符串返回该数据,在二进制模式下以字节对象返回该数据。size
是一个可选的数值参数,当 size
被省略或为负数时,将会读取并返回整个文件内容,如果文件内容大小是机器内存大小的两倍,则会读取并返回最大数量的字符数(文本模式下)或字节数(二进制模式下),开发者自己需要注意这一点。如果到达文件的末尾,f.read()
将返回一个空字符串 ''
。
>>> f.read()
'This is the entire file.\n'
>>> f.read()
''
f.readline()
方法会从文件读取一个单行字符串,还会在字符串末尾加一个换行字符 \n
,如果文件不以换行符结尾,则读取到该文件的最后一行时不会在其末尾加换行字符 \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)
方法将字符串内容写入文件,并返回写入的字符数量。
>>> 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()
方法以整数形式返回文件对象在文件中的当前位置,该位置在二进制模式下以距离到文件开头的字节数表示,在文本模式下以一个 opaque number
表示。
可以用 f.seek(offset, whence)
方法更改文件对象的位置。该位置是由 offset
加上一个参考点计算得出的,参考点是由 whence
参数选择的。
whence
为 0 表示以文件开头作为参考点;whence
为 1 表示以当前文件位置作为参考点;whence
为 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'
在文本文件中(打开文件时
mode
参数中没有b
),只允许相对于文件开头或末尾更改当前位置,只有从f.tell()
返回的值或零才能作为有效的offset
,任何其他的offset
都会产生undefined
行为。
文件对象还有一些不常用的方法,比如 isatty()
和 truncate()
,完整的文件对象指南可以参考 Library Reference。
用 json 保存结构化数据
可以从文件轻易读写字符串,读取数字要麻烦些,因为 read()
方法只返回字符串,所以必须把数字字符串用像 int() 这样的函数转成数值。如果想将嵌套列表和字典等更复杂的数据类型写入文件,那么手动解析和序列化就会变得复杂。
用户不用通过持续地编写和调试代码来将复杂的数据类型保存到文件,Python 允许使用流行的数据交换格式 JSON (JavaScript Object Notation)。标准模块 json 可以接受 Python 数据层次结构,并将其转换为字符串表示,这个过程称为序列化。从字符串表示中重新构造数据对象称为反序列化。在序列化和反序列化之间,表示对象的字符串可能已经存储在文件或数据中,或者通过网络连接发送到某个远处的计算机上。
注意:现代应用程序通常使用 JSON 格式来进行数据交换。这被许多程序员所熟悉,且使得它成为互操作性的好选择。
用简单代码查看对象 x
的 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 序列化任意类实例需要更多操作,可以参考 json 模块。
pickle 模块:
与 JSON 相反,pickle 协议支持任意复杂的 Python 对象的序列化。它是 Python 特有的,不能用于与用其他语言编写的应用程序进行通信。默认情况下,它也是不安全的,如果数据由熟练的攻击者制作,那么反序列化来自不受信任源的 pickle 数据可以执行任意代码。