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

If you have a really long format string that you don't want to split
up, it would be nice if you could reference the variables to be
formatted by name instead of by position.  This can be done by simply
passing the dict and using square brackets "'[]'" to access the keys

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


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

The "%" operator can also be used for string formatting. It interprets
the left argument much like a "sprintf()"-style format string to be
applied to the right argument, and returns the string resulting from
this formatting operation. For example:

   >>> 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" 的文件对象。

To read a file's contents, call "f.read(size)", which reads some
quantity of data and returns it as a string (in text mode) or bytes
object (in binary mode). *size* is an optional numeric argument.  When
*size* is omitted or negative, the entire contents of the file will be
read and returned; it's your problem if the file is twice as large as
your machine's memory. Otherwise, at most *size* bytes are read and
returned. If the end of the file has been reached, "f.read()" will
return an empty string ("''").

   >>> 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()" 返回一个整数，给出文件对象在文件中的当前位置，表示为二进制
模式下时从文件开始的字节数，以及文本模式下的意义不明的数字。

To change the file object's position, use "f.seek(offset, from_what)".
The position is computed from adding *offset* to a reference point;
the reference point is selected by the *from_what* argument.  A
*from_what* value of 0 measures from the beginning of the file, 1 uses
the current file position, and 2 uses the end of the file as the
reference point.  *from_what* can be omitted and defaults to 0, using
the beginning of the file as the reference point.

   >>> 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 数据可以执行任意代码。
