7. 输入输出
***********

有几种方法可以显示程序的输出；数据可以以人类可读的形式打印出来，或者写
入文件以供将来使用。本章将讨论一些可能性。


7.1. 更漂亮的输出格式
=====================

到目前为止，我们遇到了两种写入值的方法：*表达式语句* 和 "print()" 函数
。（第三种是使用文件对象的 "write()" 方法；标准输出文件可以作为
"sys.stdout" 引用。更多相关信息可参考标准库指南。）

Often you'll want more control over the formatting of your output than
simply printing space-separated values.  There are two ways to format
your output; the first way is to do all the string handling yourself;
using string slicing and concatenation operations you can create any
layout you can imagine.  The string type has some methods that perform
useful operations for padding strings to a given column width; these
will be discussed shortly.  The second way is to use formatted string
literals, or the "str.format()" method.

The "string" module contains a "Template" class which offers yet
another way to substitute values into strings.

One question remains, of course: how do you convert values to strings?
Luckily, Python has ways to convert any value to a string: pass it to
the "repr()" or "str()" functions.

"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'))"

Here are two ways to write a table of squares and cubes:

   >>> 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

   >>> 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

(Note that in the first example, one space between each column was
added by the way "print()" works: by default it adds spaces between
its arguments.)

This example demonstrates the "str.rjust()" method of string objects,
which right-justifies a string in a field of a given width by padding
it with spaces on the left.  There are similar methods "str.ljust()"
and "str.center()".  These methods do not write anything, they just
return a new string.  If the input string is too long, they don't
truncate it, but return it unchanged; this will mess up your column
lay-out but that's usually better than the alternative, which would be
lying about a value.  (If you really want truncation you can always
add a slice operation, as in "x.ljust(n)[:n]".)

还有另外一个方法，"str.zfill()" ，它会在数字字符串的左边填充零。它能识
别正负号:

   >>> '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!"

花括号和其中的字符（称为格式字段）将替换为传递给 "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.

"'!a'" (apply "ascii()"), "'!s'" (apply "str()") and "'!r'" (apply
"repr()") can be used to convert the value before it is formatted:

   >>> contents = 'eels'
   >>> print('My hovercraft is full of {}.'.format(contents))
   My hovercraft is full of eels.
   >>> print('My hovercraft is full of {!r}.'.format(contents))
   My hovercraft is full of 'eels'.

An optional "':'" and format specifier can follow the field name. This
allows greater control over how the value is formatted.  The following
example rounds Pi to three places after the decimal.

>>> import math
>>> print('The value of PI is approximately {0:.3f}.'.format(math.pi))
The value of PI is approximately 3.142.

Passing an integer after the "':'" will cause that field to be a
minimum number of characters wide.  This is useful for making tables
pretty.

   >>> 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 = {'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()" 进行字符串格式化的完整概述，请参阅 格式字符串
语法 。


7.1.1. 旧的字符串格式化方法
---------------------------

"%" 操作符也可以用作字符串格式化。它将左边的参数解释为一个很像
"sprintf()" 风格 的格式字符串，应用到右边的参数，并返回一个由此格式化
操作产生的字符串。例如:

   >>> import math
   >>> print('The value of PI is approximately %5.3f.' % math.pi)
   The value of PI is approximately 3.142.

可在 printf 风格的字符串格式化 部分找到更多信息。


7.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" 文件中的数据。请一定要
注意在读写此类文件时应使用二进制模式。

It is good practice to use the "with" keyword when dealing with file
objects.  The advantage is that the file is properly closed after its
suite finishes, even if an exception is raised at some point.  Using
"with" is also much shorter than writing equivalent "try"-"finally"
blocks:

   >>> with open('workfile') as f:
   ...     read_data = f.read()
   >>> f.closed
   True

如果你没有使用 "with" 关键字，那么你应该调用 "f.close()" 来关闭文件并
立即释放它使用的所有系统资源。如果你没有显式地关闭文件，Python的垃圾回
收器最终将销毁该对象并为你关闭打开的文件，但这个文件可能会保持打开状态
一段时间。另外一个风险是不同的Python实现会在不同的时间进行清理。

通过 "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.


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()
   ''

要从文件中读取行，你可以循环遍历文件对象。这是内存高效，快速的，并简化
代码:

   >>> 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, from_what)" 。通过向参考
点添加 *offset* 来计算位置；参考点由 *from_what* 参数指定。*from_what*
值为0时，表示从文件开头开始，1 表示从当前位置，2 表示把文件末尾作为参
考点。*from_what* 可以省略，默认为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)" 搜索到文件末尾是个例外）并且唯一有效的
*offset* 值是那些能从 "f.tell()" 中返回的或者是零。其他 *offset* 值都
会产生未定义的行为。

文件对象有一些额外的方法，例如 "isatty()" 和 "truncate()" ，它们使用频
率较低；有关文件对象的完整指南请参阅库参考。


7.2.2. 使用 "json" 保存结构化数据
---------------------------------

字符串可以很轻松地写入文件并从文件中读取出来。数字可能会费点劲，因为
"read()" 方法只能返回字符串，这些字符串必须传递给类似 "int()" 的函数，
它会接受类似 "'123'" 这样的字符串并返回其数字值 123。当你想保存诸如嵌
套列表和字典这样更复杂的数据类型时，手动解析和序列化会变得复杂。

Python 允许你使用称为  JSON (JavaScript Object Notation) 的流行数据交
换格式，而不是让用户不断的编写和调试代码以将复杂的数据类型保存到文件中
。名为 "json" 的标准模块可以采用 Python 数据层次结构，并将它们转化为字
符串表示形式；这个过程称为 *serializing* 。从字符串表示中重建数据称为
*deserializing* 。在序列化和反序列化之间，表示对象的字符串可能已存储在
文件或数据中，或通过网络连接发送到某个远程机器。

注解: JSON格式通常被现代应用程序用于允许数据交换。许多程序员已经熟悉
  它，这 使其成为互操作性的良好选择。

如果你有一个对象 "x" ，你可以用一行简单的代码来查看它的 JSON 字符串表
示:

   >>> import json
   >>> json.dumps([1, 'simple', 'list'])
   '[1, "simple", "list"]'

"dumps()" 函数的另一个变体叫做 "dump()" ，它只是将对象序列化为 *text
file* 。因此，如果 "f" 是一个 *text file* 对象，我们可以这样做:

   json.dump(x, f)

要再次解码对象，如果 "f" 是一个打开的以供阅读的 *text file* 对象:

   x = json.load(f)

这种简单的序列化技术可以处理列表和字典，但是在JSON中序列化任意类的实例
需要额外的努力。 "json" 模块的参考包含对此的解释。

参见: "pickle" - 封存模块

  与 JSON 不同，*pickle* 是一种允许对任意复杂 Python 对象进行序列化的
  协议。因此，它为 Python 所特有，不能用于与其他语言编写的应用程序通信
  。默认情况下它也是不安全的：如果数据是由熟练的攻击者精心设计的，则反
  序列化来自不受信任来源的 pickle 数据可以执行任意代码。
