10.2. 标准输入、输出和错误

UNIX 用户已经熟悉标准输入、标准输出和标准错误的概念。本节面向其他用户。

标准输出和标准错误(通常缩写为 stdoutstderr)是每个 UNIX 系统内置的管道。当您 print 打印内容时,它会进入 stdout 管道;当您的程序崩溃并打印调试信息(如 Python 中的回溯)时,它会进入 stderr 管道。这两个管道通常都连接到您正在使用的终端窗口,因此当程序打印时,您会看到输出,而当程序崩溃时,您会看到调试信息。(如果您使用的是基于窗口的 Python IDE,则 stdoutstderr 默认为您的“交互式窗口”。)

示例 10.8. 介绍 stdoutstderr

>>> for i in range(3):
...     print 'Dive in'             1
Dive in
Dive in
Dive in
>>> import sys
>>> for i in range(3):
...     sys.stdout.write('Dive in') 2
Dive inDive inDive in
>>> for i in range(3):
...     sys.stderr.write('Dive in') 3
Dive inDive inDive in
1 正如您在示例 6.9,“简单计数器” 中看到的,您可以使用 Python 的内置 range 函数来构建简单的计数器循环,以重复执行某个操作一定次数。
2 stdout 是一个类似文件的对象;调用其 write 函数将打印您提供的任何字符串。实际上,这就是 print 函数的真正作用;它会在您要打印的字符串末尾添加一个回车符,并调用 sys.stdout.write
3 在最简单的情况下,stdoutstderr 将其输出发送到同一个位置:Python IDE(如果您在其中)或终端(如果您从命令行运行 Python)。与 stdout 一样,stderr 不会为您添加回车符;如果您需要,请自行添加。

stdoutstderr 都是类似文件的对象,就像您在第 10.1 节“抽象输入源” 中讨论的那样,但它们都是只写的。它们没有 read 方法,只有 write 方法。尽管如此,它们仍然是类似文件的对象,您可以将任何其他文件或类似文件的对象分配给它们以重定向其输出。

示例 10.9. 重定向输出

[you@localhost kgp]$ python stdout.py
Dive in
[you@localhost kgp]$ cat out.log
This message will be logged instead of displayed

(在 Windows 上,您可以使用 type 而不是 cat 来显示文件的内容。)

如果您尚未下载,则可以下载本书中使用的此示例和其他示例

#stdout.py
import sys

print 'Dive in'                                          1
saveout = sys.stdout                                     2
fsock = open('out.log', 'w')                             3
sys.stdout = fsock                                       4
print 'This message will be logged instead of displayed' 5
sys.stdout = saveout                                     6
fsock.close()                                            7
1 这将在 IDE 的“交互式窗口”(或终端,如果从命令行运行脚本)中打印。
2 在重定向 stdout 之前始终保存它,以便以后可以将其恢复正常。
3 打开一个文件以进行写入。如果文件不存在,则会创建该文件。如果文件已存在,则会被覆盖。
4 将所有后续输出重定向到您刚打开的新文件。
5 这将仅“打印”到日志文件;它在 IDE 窗口或屏幕上不可见。
6 stdout 设置回您对其进行更改之前的状态。
7 关闭日志文件。

重定向 stderr 的方法完全相同,使用 sys.stderr 而不是 sys.stdout

示例 10.10. 重定向错误信息

[you@localhost kgp]$ python stderr.py
[you@localhost kgp]$ cat error.log
Traceback (most recent line last):
  File "stderr.py", line 5, in ?
    raise Exception, 'this error will be logged'
Exception: this error will be logged

如果您尚未下载,则可以下载本书中使用的此示例和其他示例

#stderr.py
import sys

fsock = open('error.log', 'w')               1
sys.stderr = fsock                           2
raise Exception, 'this error will be logged' 3 4
1 打开要存储调试信息的日志文件。
2 通过将新打开的日志文件的 file 对象分配给 stderr 来重定向标准错误。
3 引发异常。请注意,从屏幕输出中可以看出,这不会在屏幕上打印任何内容。所有正常的回溯信息都已写入 error.log
4 还要注意,您没有显式关闭日志文件,也没有将 stderr 设置回其原始值。这很好,因为一旦程序崩溃(由于异常),Python 将为我们清理并关闭文件,并且 stderr 从未恢复也无关紧要,因为正如我提到的,程序崩溃了,Python 结束了。如果您希望之后在同一个脚本中执行其他操作,则恢复原始值对于 stdout 更为重要。

由于将错误消息写入标准错误非常常见,因此可以使用一种简写语法,而不必完全重定向它。

示例 10.11. 打印到 stderr

>>> print 'entering function'
entering function
>>> import sys
>>> print >> sys.stderr, 'entering function' 1
entering function
1 print 语句的这种简写语法可用于写入任何打开的文件或类似文件的对象。在这种情况下,您可以将单个 print 语句重定向到 stderr,而不会影响后续的 print 语句。

另一方面,标准输入是一个只读文件对象,它表示从某个先前程序流入程序的数据。对于经典的 Mac OS 用户,甚至 Windows 用户来说,这可能没有多大意义,除非您曾经精通 MS-DOS 命令行。它的工作方式是,您可以在一行中构建一个命令链,以便一个程序的输出成为链中下一个程序的输入。第一个程序只是输出到标准输出(自身不进行任何特殊重定向,只是执行正常的 print 语句或其他操作),下一个程序从标准输入读取,操作系统负责将一个程序的输出连接到下一个程序的输入。

示例 10.12. 链接命令

[you@localhost kgp]$ python kgp.py -g binary.xml         1
01100111
[you@localhost kgp]$ cat binary.xml                      2
<?xml version="1.0"?>
<!DOCTYPE grammar PUBLIC "-//diveintopythonbook.pythonlang.cn//DTD Kant Generator Pro v1.0//EN" "kgp.dtd">
<grammar>
<ref id="bit">
  <p>0</p>
  <p>1</p>
</ref>
<ref id="byte">
  <p><xref id="bit"/><xref id="bit"/><xref id="bit"/><xref id="bit"/>\
<xref id="bit"/><xref id="bit"/><xref id="bit"/><xref id="bit"/></p>
</ref>
</grammar>
[you@localhost kgp]$ cat binary.xml | python kgp.py -g - 3 4
10110001
1 正如您在第 9.1 节“深入” 中看到的,这将打印一个由八个随机位组成的字符串,01
2 这只是简单地打印出 binary.xml 的全部内容。(Windows 用户应使用 type 而不是 cat。)
3 这将打印 binary.xml 的内容,但“|”字符(称为“管道”字符)表示内容不会打印到屏幕上。相反,它们将成为下一个命令的标准输入,在本例中,该命令调用您的 Python 脚本。
4 您没有指定模块(如 binary.xml),而是指定了“-”,这会导致您的脚本从标准输入而不是磁盘上的特定文件加载语法。(有关如何实现这一点的更多信息,请参见下一个示例。)因此,效果与第一个语法相同,在第一个语法中,您直接指定了语法文件名,但请考虑这里的扩展可能性。您可以运行一个动态生成语法的脚本,而不是简单地执行 cat binary.xml,然后您可以将其通过管道传输到您的脚本中。它可以来自任何地方:数据库、某些语法生成元脚本,等等。关键是您根本不需要更改 kgp.py 脚本就可以合并任何这些功能。您需要做的就是能够从标准输入中获取语法文件,并且您可以将所有其他逻辑分离到另一个程序中。

那么,当语法文件为“-”时,脚本如何“知道”从标准输入读取?这不是魔术;这只是代码。

示例 10.13. 在 kgp.py 中从标准输入读取


def openAnything(source):
    if source == "-":    1
        import sys
        return sys.stdin

    # try to open with urllib (if source is http, ftp, or file URL)
    import urllib
    try:

[... snip ...]
1 这是来自 toolbox.pyopenAnything 函数,您之前在“10.1. 抽象化输入源”中检查过。您所做的只是在函数开头添加了三行代码,用于检查源是否为“-”。如果是,则返回 sys.stdin。真的,就是这样!请记住,stdin 是一个具有 read 方法的类文件对象,因此其余代码(在调用 openAnythingkgp.py 中)一点也没有改变。