5.8. 标准输入,输出和错误

UNIX用户已经对标准输入、标准输出和标准错误的概念熟悉了。这一节是为其它不熟悉的人准备的。

标准输出和标准错误(通常缩写为 stdoutstderr)是建立在每个UNIX系统内的管道(pipe)。当你 print 某东西时,结果输出到 stdout 管道中;当你的程序崩溃并打印出调试信息时(象Python中的错误跟踪),结果输出到 stderr 管道中。通常这两个管道只与你正在工作的终端窗口相联,所以当一个程序打印输出时,你可以看到输出,并且当一个程序崩溃时,你可以看到调试信息。(如果你在一个基于窗口的Python IDE系统上工作,stdoutstderr 缺省为“交互窗口”。)

例 5.32. 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

正如我们在例 3.28中看到的,我们可以使用Python内置的 range 函数来创建简单的计数循环,即重复某物一定的次数。

2

stdout 是一个类文件对象;调用它的 write 函数会打印出任何给出的字符串。事实上,这就是 print 函数真正所做的;它会在正打印的字符串后面加上回车换行符,并调用 sys.stdout.write

3

在最简单的例子中,stdoutstderr 将它们的输出发送到同一个地方:Python IDE,或终端(如果你正从命令行运行Python)。象 stdoutstderr 并不为你增加回车换行符;如果需要,要自已加上。

stdoutstderr 都是类文件对象,就象我们在提取输入源中所讨论的一样,但它们都是只写的。它们没有 read 方法,只有 write。然而,它们的确是类文件对象,并且你可以将任意文件对象或类文件对象赋给它们来重定向输出。

例 5.33. 重定向输出

[f8dy@oliver kgp]$ python2 stdout.py
Dive in
[f8dy@oliver kgp]$ cat out.log
This message will be logged instead of displayed

如果你还没有这样做,你可以下载本书中用到的本例和其它例子

#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

例 5.34. 重定向错误信息

[f8dy@oliver kgp]$ python2 stderr.py
[f8dy@oliver 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

将我们新打开的日志文件的文件对象赋给 stderr 重定向标准错误。

3

引发一个异常。从屏幕输出上我们可以注意到这样没有在屏幕上打印出任何东西。所以正常跟踪信息已经写进 error.log

4

还要注意我们既没有显示地关闭日志文件,也没有将 stderr 设回它的初始值。这样挺好,因为一旦程序崩溃(由于我们的异常),Python将替我们清理和关闭文件,并且 stderr 永远不恢复不会造成什么不同。因为,我提到过,一旦程序崩溃,则Python也结束。如果你希望在同一个脚本的后面去做其它的事情,恢复初始值对 stdout 更为重要。

另一方面,标准输入是只读文件对象,同时它表示从前面某个程序的数据流入这个程序。这一点可能对典型的Mac OS用户可能没什么意义,或者甚至是对Windows用户也是如此,除非你更习惯在MS-DOS命令行下工作。它的工作方式是:你可以在单个文件中构造一个命令行的链,这样一个程序的输出成为链中下一个程序的输入。第一个程序简单地输出到标准输出(本身不需要任何特别的重定义,只是执行正常的 print 什么的),同时下个程序从标准输入读入,操作系统会小心地将一个程序的输出连接到下一个程序的输入。

例 5.35. 链接命令

[f8dy@oliver kgp]$ python2 kgp.py -g binary.xml         1
01100111
[f8dy@oliver kgp]$ cat binary.xml                       2
<?xml version="1.0"?>
<!DOCTYPE grammar PUBLIC "-//diveintopython.org//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>
[f8dy@oliver kgp]$ cat binary.xml | python2 kgp.py -g - 3 4
10110001
1

正如我们在接触中看到的,这样会打印出8个随机比特(01)的字符串。

2

这样会简单地打印出 binary.xml 的全部内容。(Windows用户应该使用 type 代替 cat。)

3

这样会打印 binary.xml 的内容,但是“|”字符,叫做管道符,表示输出内容不会打印到屏幕上。相反,它们成为下个命令(在本例中调用我们的Python脚本)的标准输入。

4

我们没有指定一个模块(象 binary.xml),而是指定“-”,这会让我们的脚本从标准输入而不是从磁盘上的一个特别文件中装入语法。(在下个例子中有更多关于它是如何发生的内容。)这样效果同第一个语法(我们直接指定语法文件名)是一样的,但它考虑了这里的扩展的可能性。不只是简单地执行 cat binary.xml,我们可以运行一个可以动态生成语法的脚本,然后可以将它通过管道输入到我们的脚本中。语法可以来自任何地方:数据库,或某个语法生成元脚本什么的。要点就是我们完全不必修改我们的 kgp.py 脚本就可以同任何这种功能进行合并。我们要做的只是能够从标准输入中接收语法文件,并且我们可以将所有其它的逻辑分散到另一个程序中。

那么当语法文件是“-”时我们的脚本是如何中从标准输入读入的呢?没什么神秘的,就是编码。

例 5.36. 在 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:

[... 略 ...]
1

这是来自 toolbox.py 中的 openAnything 函数,我们在提取输入源中讨论过的。我们所做的全部是在函数的开始处添加三行代码,用来检查是否 source 是“-”,如果是,我们返回 sys.stdin。实际上,就是这样!记住,stdin 是一个带有 read 方法的类文件对象,所以我们代码的其余部分(在 kgp.py 中,我们调用 openAnything 的地方)一点没有改变。