《Python入门教程》7. 输入和输出

  1. 开胃菜
  2. 使用 Python 解释器
  3. 非正式介绍Python
  4. 深入流程控制
  5. 数据结构
  6. 模块
  7. 输入和输出
  8. 错误和异常
  9. 标准库的简明介绍
  10. 标准库的简明介绍(第二部分)
  11. 现在干什么?
  12. 交互式输入编辑及历史替代
  13. 浮点算术: 问题和限制

有多种方式可以展现一个程序的输出; 数据可以以一种可读的形式输出, 或者是保存于一个文件便于以后使用. 本章就将讨论几种可能.

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.

留下评论

您的邮箱地址不会被公开。 必填项已用 * 标注