- 开胃菜
- 使用 Python 解释器
- 非正式介绍Python
- 深入流程控制
- 数据结构
- 模块
- 输入和输出
- 错误和异常
- 类
- 标准库的简明介绍
- 标准库的简明介绍(第二部分)
- 现在干什么?
- 交互式输入编辑及历史替代
- 浮点算术: 问题和限制
有多种方式可以展现一个程序的输出; 数据可以以一种可读的形式输出, 或者是保存于一个文件便于以后使用. 本章就将讨论几种可能.
7.1. 输出格式美化
至今为止, 我们知道了两种输出值的方式: 表达式语句 和 print()
函数. (第三种方式是使用文件对象的 write()
方法; 标准输出文件可以用 sys.stdout
引用. 参考库手册了解更多的信息.)
一般来说你会希望更多的控制其输出格式, 而不是简单的以空格分割. 有两种方式格式化你的输出; 第一种方式是由你自己控制; 使用字符串切片和连接操作, 来实现你所想象的外观. 标准模块 string
包含了一些有用的操作, 用以填充字符串至某一给定的宽度; 很快就会讨论这些. 第二种方式是使用 str.format()
方法.
string
模块包含了一个类模板, 提供了另一种替换字符串的方式.
还有一个问题, 当然了: 如何把值转成字符串? 幸运的是, Python 有多种方式将任何值转为字符串: 将它传给 repr()
或 str()
函数.
str()
函数意味着返回一个用户易读的表达形式, 而 repr()
则意味着产生一个解释器易读的表达形式 (或者如果没有这样的语法会给出 SyntaxError
). 对于那些没有特殊表达的对象, str()
将会与 repr()
返回相同的值. 很多的值, 如数字或一些如列表和字典那样的结构, 使用这两个函数的结果完全一致. 字符串与浮点型则有两种不同的表达.
例如:
>>> s = ‘Hello, world.’
>>> str(s)
‘Hello, world.’
>>> repr(s)
“‘Hello, world.’”
>>> str(1.0/7.0)
‘0.142857142857’
>>> repr(1.0/7.0)
‘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’))”
这里有两种方式输出一个平方与立方的表:
(注意在第一个例子中, 每列间的空格是由 print()
添加的: 它总会在每个参数后面加个空格.)
这个例子展示了字符串对象的 rjust()
方法, 它可以将字符串靠右, 并在左边填充空格. 还有类似的方法, 如 ljust()
和 center()
. 这些方法并不会写任何东西, 它们仅仅返回新的字符串. 如果输入很长, 它们并不会对字符串进行截断, 仅仅返回没有任何变化的字符串; 这虽然会影响你的布局, 但是这一般比截断的要好. (如果你的确需要截断, 那么就增加一个切片的操作, 如 x.ljust(n)[:n]
.)
有另一个方法, zfill()
, 它会在数字的左边填充 0. 它知道正负号:
>>> ’12’.zfill(5)
‘00012’
>>> ‘-3.14’.zfill(7)
‘-003.14’
>>> ‘3.14159265359’.zfill(5)
‘3.14159265359’
str.format()
的基本使用如下:
>>> print(‘We are the {} who say “{}!”‘.format(‘knights’, ‘Ni’))
We are the knights who say “Ni!”
括号及其里面的字符 (称作 format field) 将会被 format()
中的参数替换. 在括号中的数字用于指向传入对象在 format()
中的位置.
>>> print(‘{0} and {1}‘.format(‘spam’, ‘eggs’))
spam and eggs
>>> print(‘{1} and {0}‘.format(‘spam’, ‘eggs’))
eggs and spam
如果在 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.
'!a'
(使用 ascii()
), '!s'
(使用 str()
) 和 '!r'
(使用 repr()
) 可以用于在格式化某个值之前对其进行转化:
>>> importmath
>>> print(‘The value of PI is approximately {}.’.format(math.pi))
The value of PI is approximately 3.14159265359.
>>> print(‘The value of PI is approximately {!r}.’.format(math.pi))
The value of PI is approximately 3.141592653589793.
可选项 ':'
和格式标识符可以跟着 field name. 这就允许对值进行更好的格式化. 下面的例子将 Pi 保留到小数点后三位.
>>> importmath
>>> print(‘The value of PI is approximately {0:.3f}.’.format(math.pi))
The value of PI is approximately 3.142.
在 ':'
后传入一个整数, 可以保证该域至少有这么多的宽度. 用于美化表格时很有用.
>>> table = {‘Sjoerd’: 4127, ‘Jack’: 4098, ‘Dcab’: 7678}
>>> for name, phone in table.items():
… print(‘{0:10} ==> {1:10d}‘.format(name, phone))
…
Jack ==> 4098
Dcab ==> 7678
Sjoerd ==> 4127
如果你有一个的确很长的格式化字符串, 而你不想将它们分开, 那么在格式化时通过变量名而非位置会是很好的事情. 最简单的就是传入一个字典, 然后使用方括号 '[]'
来访问键值 :
>>> 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
在结合新的内置函数 vars()
(这会以字典的形式返回所有的局部变量) 和这个时会特别有用.
要了解更多关于 str.format()
的知识, 参考 Format String Syntax.
7.1.1. 旧式字符串格式化
%
操作符也可以实现字符串格式化. 它将左边的参数作为类似 sprintf()
式的格式化字符串, 而将右边的代入, 然后返回格式化后的字符串. 例如:
>>> importmath
>>> print(‘The value of PI is approximately %5.3f.’ % math.pi)
The value of PI is approximately 3.142.
因为 str.format()
很新, 大多数的 Python 代码仍然使用 %
操作符. 但是因为这种旧式的格式化最终会从该语言中移除, 应该更多的使用 str.format()
.
更多的信息可以在 Old String Formatting Operations 中找到.
7.2. 读和写文件
open()
将会返回一个 file object, 并且一般使用两个参数进行使用: open(filename, mode)
.
>>> f = open(‘/tmp/workfile’, ‘w’)
第一个参数是包含文件名的字符串. 第二个参数是另一个字符串, 包含描述文件如何使用的字符. mode 可以是 'r'
如果文件只读, 'w'
只用于写 (如果存在同名文件则将被删除), 和 'a'
用于追加文件内容; 所写的任何数据都会被自动增加到末尾. 'r+'
同时用于读写. mode 参数是可选的; 'r'
将是默认值.
一般而言, 文件以 text mode 打开, 这就意味着, 从文件中读写的字符串, 是以一种特定的编码进行编码 (默认的是 UTF-8). 追加到 mode 后的 'b'
, 将意味着以 binary mode 打开文件: 现在的数据是以字节对象的形式进行读写. 这个模式应该用于那些不包含文本的文件.
在文本模式下 (text mode), 默认是将特定平台的行末标识符 ( Unix 下为 \n
, Windows 下为 \r\n
) 在读时转为 \n
而写时将 \n
转为特定平台的标识符. 这种隐藏的行为对于文本文件是没有问题的, 但是对于二进制数据像 JPEG
或 EXE
是会出问题的. 在使用这些文件时请小心使用二进制模式.
7.2.1. 文件对象的方法
本节中剩下的例子假设已经创建了一个称为 f
的文件对象.
为了读取一个文件的内容, 调用 f.read(size)
, 这将读取一定数目的数据, 然后作为字符串或字节对象返回. size 是一个可选的数字类型的参数. 当 size 被忽略了或者为负, 那么该文件的所有内容都将被读取并且返回; 如果文件比你的内存大两倍, 那么就会成为你的问题了. 否则, 最多 size 字节将被读取并返回. 如果到达了文件的末尾, f.read()
将会返回一个空字符串 (''
).
>>> f.read()
‘This is the entire file.\n’
>>> f.read()
”
f.readline()
会从文件中读取单独的一行; 在每个字符串的末尾都会留下换行符 (\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()
”
f.readlines()
将返回该文件中包含的所有行. 如果给定一个可选参数 sizehint, 它就读取这么多字节, 并且将这些字节按行分割. 这经常用于允许按行读取一个大文件, 但是不需要载入全部的文件时非常有用. 只会返回完整的行.
>>> f.readlines()
[‘This is the first line of the file.\n’, ‘Second line of the file\n’]
另一种方式是迭代一个文件对象然后读取每行. 这是内存有效, 快速, 并用最少的代码:
>>> for line in f:
… print(line, end=”)
…
This is the first line of the file.
Second line of the file
这个方法很简单, 但是并没有提供一个很好的控制. 因为两者的处理机制不同, 最好不要混用.
f.write(string)
将 string 写入到文件中, 然后返回写入的字符数.
>>> f.write(‘This is a test\n‘)
15
如果要写入一些不是字符串的东西, 那么将需要先进行转换:
>>> value = (‘the answer’, 42)
>>> s = str(value)
>>> f.write(s)
18
f.tell()
返回文件对象当前所处的位置, 它是从文件开头开始算起的字节数. 要改变文件当前的位置, 使用 f.seek(offset, from_what)
. 这个位置是通过将当前位置加上 offset 所得. from_what 的值, 如果是 0 表示开头, 如果是 1 表示当前位置, 2 表示文件的结尾. from_what 的默认为 0, 即从开头开始.
>>> f = open(‘/tmp/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.close()
会关闭它, 并释放系统的资源. 在调用完 f.close()
之后, 尝试使用那个文件对象是会失败的.
>>> f.close()
>>> f.read()
Traceback (most recent call last):
File “<stdin>”, line 1, in ?
ValueError: I/O operation on closed file
当处理一个文件对象时, 使用 with
关键字是非常好的方式. 在结束后, 它会帮你正确的关闭文件, 即使发生了异常. 而且写起来也比 try
– finally
语句块要简短:
>>> with open(‘/tmp/workfile’, ‘r’) as f:
… read_data = f.read()
>>> f.closed
True
文件对象有些额外的方法, 如 isatty()
和 trucate()
, 但它们都较少的使用; 更多的信息需要参考标准库手册.
7.2.2. pickle
模块
在文件中, 字符串可以很方便的读取写入. 数字可能稍微麻烦一些, 因为 read()
方法只返回字符串, 我们还需要将其传给 int()
这样的函数, 使其将如 '123'
的字符串转为数字 123. 但是, 如果要保存更复杂的数据类型, 如列表, 字典, 或者类的实例, 那么就会更复杂了.
为了让用户在时常的编程和测试时保存复杂的数据类型, Python 提供了标准模块, 称为 pickle
. 这个模块可以将几乎任何的 Python 对象 (甚至是 Python 的代码), 转换为字符串表示; 这个过程称为 pickling. 而要从里面重新构造回原来的对象, 则称为 unpickling. 在 pickling 和 unpickling 之间, 表示这些对象的字符串表示, 可以存于一个文件, 也可以通过网络在远程机器间传输.
如果你有一个对象 x
, 和一个已经打开并用于写的文件对象 f
, pickle 这个对象最简单的方式就是使用:
pickle.dump(x, f)
有了 pickle 这个对象, 就能对 f
以读取的形式打开:
x = pickle.load(f)
(还有其他不同的形式, 比如 pickling 很多对象, 或者不想保存至文件; 更多的信息参考 pickle
模块.)
pickle
是 Python 中保存及重用对象的标准方式; 标准的属于称为 persistent 对象 (即持久化对象). 因为 pickle
被广泛使用, 很多写 Python 扩展的作者都会确保, 如矩阵这样的数据类型能被合理的 pickle 和 unpickle.