有几种方法可以表示程序的输出; 数据可以以人类可读的形式打印,也可以写入文件以供将来使用。本章将讨论其中的一些可能性。
到目前为止,我们已经遇到了两种编写值的方法: 表达式语句和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-string
)允许您在字符串前加上f或f并将表达式写成{expression}
,从而在字符串中包含Python表达式的值。
表达式后面可以有一个可选的格式说明符。这允许更好地控制值的格式化方式。下面的例子将圆周率四舍五入到小数点后三位:
>>> 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'
format()
方法 (The String format()
Method)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
这里是相同的方框和立方体表,手动格式化:
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()
,它在数值字符串左侧填充0。它能理解正负号:
>>> '12'.zfill(5)
'00012'
>>> '-3.14'.zfill(7)
'-003.14'
>>> '3.14159265359'.zfill(5)
'3.14159265359'
%
运算符(取模)也可用于字符串格式化。给定'string' %values
,string
中%
的实例将被替换为0个或多个values
元素。这种操作通常被称为字符串插值。例如:
>>> import math
>>> print('The value of pi is approximately %5.3f.' % math.pi)
The value of pi is approximately 3.142.
open()
返回一个文件对象,通常使用两个位置参数和一个关键字参数:open(filename, mode, encoding=None)
f = open('workfile', 'w', encoding="utf-8")
第一个参数是一个包含文件名的字符串。第二个参数是另一个字符串,包含一些字符,描述文件的使用方式。mode
可以是’r
’表示文件只被读取,'w
’表示只被写入(一个已经存在的同名文件将被擦除),'a
’表示打开文件用于追加;任何写入文件的数据都会自动添加到文件末尾。r+
打开文件进行读和写。mode
参数是可选的;如果省略了r
,则默认为r
。通常,文件是以文本模式打开的,这意味着你可以从文件读写字符串,这些字符串以特定的编码方式编码。如果没有指定编码,则默认值取决于平台。因为UTF-8是现代事实上的标准,所以我们推荐encoding=" UTF-8 "
,除非你知道需要使用不同的编码。给模式添加一个’b
’将以二进制模式打开文件。二进制模式的数据以bytes对象的形式读写。以二进制模式打开文件时,不能指定编码。在文本模式下,读取时的默认操作是将特定于平台的行结尾(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()
# We can check that the file has been automatically closed.
>>> f.closed
True
如果您没有使用with关键字,那么您应该调用f.close()
来关闭文件并立即释放它使用的所有系统资源。
警告: 不使用with关键字调用f.write()
或调用f.close()
可能导致f.write()
的参数没有完全写入磁盘,即使程序成功退出。
>>> f.close()
>>> f.read()
Traceback (most recent call last):
File "" , line 1, in <module>
ValueError: I/O operation on closed file.
在通过with语句或调用f.close()
关闭文件对象后,尝试使用该文件对象将自动失败。
>>> f.close()
>>> f.read()
Traceback (most recent call last):
File "" , 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
)留在字符串的末尾,如果文件不以换行符结束,则只在文件的最后一行被省略。这使得返回值明确无误;如果f.r ereadline()
返回一个空字符串,则表示已经到达文件的末尾,而空行则由’\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, 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)
查找到文件的末尾),惟一有效的偏移值是从f.t tell()
返回的,或者为零。任何其他偏移值都会产生未定义的行为。
文件对象有一些额外的方法,例如isatty()
和truncate()
,它们不太常用;参考库参考文件对象的完整指南。
json
保存结构化数据 (Saving structured data with json)可以很容易地向文件写入和从文件中读取字符串。但是对于数字就比较困难,因为read()
方法只返回字符串,必须将其传递给int()
这样的函数,后者接受'123'
这样的字符串并返回其数值123
。当您想要保存更复杂的数据类型(如嵌套列表和字典)时,手动解析和序列化就变得复杂起来。
与其让用户不断地编写和调试代码以将复杂的数据类型保存到文件中,Python允许您使用称为JSON
(JavaScript Object Notion)的流行数据交换格式。名为json的标准模块可以接受Python数据层次结构,并将它们转换为字符串表示形式;这个过程称为序列化。从字符串表示形式重构数据称为反序列化。在序列化和反序列化之间,表示对象的字符串可能已经存储在文件或数据中,或通过网络连接发送到某个远程mac
如果你有一个对象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模块的引用包含对此的解释。