详解python的⼏种标准输出重定向⽅式
⼀. 背景
在Python中,⽂件对象sys.stdin、sys.stdout和sys.stderr分别对应解释器的标准输⼊、标准输出和标准出错流。在程序启动时,这些对象的初值由sys.__stdin__、sys.__stdout__和sys.__stderr__保存,以便⽤于收尾(finalization)时恢复标准流对象。
Windows系统中IDLE(Python GUI)由,该GUI没有控制台。因此,IDLE将标准输出句柄替换为特殊的PseudoOutputFile对象,以便脚本输出重定向到IDLE终端窗⼝(Shell)。这可能导致⼀些奇怪的问题,例如:
Python 2.7.11 (v2.7.11:6d1b6a68f775, Dec 5 2015, 20:32:19) [MSC v.1500 32 bit (Intel)] on win32
Type "copyright", "credits" or "license()" for more information.
>>> import sys
>>> for fd in (sys.stdin, sys.stdout, sys.stderr): print fd
<idlelib.PyShell.PseudoInputFile object at 0x0177C910>
<idlelib.PyShell.PseudoOutputFile object at 0x0177C970>
<idlelib.PyShell.PseudoOutputFile object at 0x017852B0>
>>> for fd in (sys.__stdin__, sys.__stdout__, sys.__stderr__): print fd
<open file '<stdin>', mode 'r' at 0x00FED020>
<open file '<stdout>', mode 'w' at 0x00FED078>
<open file '<stderr>', mode 'w' at 0x00FED0D0>
>>>
可以发现,sys.__stdout__与sys.stdout取值并不相同。⽽在普通的Python解释器下(如通过Windows控制台)运⾏上述代码时,两者取值相同。
print语句(statement)不以逗号结尾时,会在输出字符串尾部⾃动附加⼀个换⾏符(linefeed);否则将⼀个空格代替附加的换⾏符。print语句默认写⼊标准输出流,也可重定向⾄⽂件或其他可写对象(所有提供write⽅法的对象)。这样,就可以使⽤简洁的print语句代替笨拙的object.write('hello'+'\n')写法。
由上可知,在Python中调⽤print obj打印对象时,缺省情况下等效于调⽤sys.stdout.write(obj+'\n')
⽰例如下:
>>> import sys
>>> print 'Hello World'
Hello World
>>> sys.stdout.write('Hello World')
Hello World
⼆. 重定向⽅式
本节介绍常⽤的Python标准输出重定向⽅式。这些⽅法各有优劣之处,适⽤于不同的场景。
2.1 控制台重定向
最简单常⽤的输出重定向⽅式是利⽤控制台命令。这种重定向由控制台完成,⽽与Python本⾝⽆关。
Windows命令提⽰符()和Linux Shell(bash等)均通过">"或">>"将输出重定向。其中,">"表⽰覆盖内容,">>"表⽰追加内容。类似地,"2>"可重定向标准错误。重定向到"nul"(Windows)或"/dev/null"(Linux)会抑制输出,既不屏显也不存盘。
以Windows命令提⽰符为例,将Python脚本输出重定向到⽂件(为缩短篇幅已删除命令间空⾏):
E:\>echo print 'hello' > test.py
E:\>test.py >
E:\>
hello
E:\>test.py >>
E:\>
hello
hello
E:\>test.py > nul
注意,在Windows命令提⽰符中执⾏Python脚本时,命令⾏⽆需以"python"开头,系统会根据脚本后缀⾃动调⽤Python解释器。此外,type命令可直接显⽰⽂本⽂件的内容,类似Linux系统的cat命令。
Linux Shell中执⾏Python脚本时,命令⾏应以"python"开头。除">"或">>"重定向外,还可使⽤tee命令。该命令可将内容同时输出到终端屏幕和(多个)⽂件中,"-a"选项表⽰追加写⼊,否则覆盖写⼊。⽰例如下(echo $SHELL或echo $0显⽰当前所使⽤的
Shell):
[wangxiaoyuan_@localhost ~]$ echo $SHELL
/bin/bash
[wangxiaoyuan_@localhost ~]$ python -c "print 'hello'"
hello
[wangxiaoyuan_@localhost ~]$ python -c "print 'hello'" >
[wangxiaoyuan_@localhost ~]$
hello
[wangxiaoyuan_@localhost ~]$ python -c "print 'world'" >>
[wangxiaoyuan_@localhost ~]$
hello
world
[wangxiaoyuan_@localhost ~]$ python -c "print 'I am'" |
I am
[wangxiaoyuan_@localhost ~]$ python -c "print 'xywang'" | tee -
xywang
[wangxiaoyuan_@localhost ~]$
I am
xywang
[wangxiaoyuan_@localhost ~]$ python -c "print 'hello'" > /dev/null
[wangxiaoyuan_@localhost ~]$
若仅仅想要将脚本输出保存到⽂件中,也可直接借助会话窗⼝的⽇志抓取功能。
注意,控制台重定向的影响是全局性的,仅适⽤于⽐较简单的输出任务。
2.2 print >>重定向
这种⽅式基于print语句的扩展形式,即"print obj >> expr"。其中,obj为⼀个file-like(尤其是提供write⽅法的)对象,为None时对应标准输出(sys.stdout)。expr将被输出到该⽂件对象中。
⽰例如下:
memo = cStringIO.StringIO(); serr = sys.stderr; file = open('', 'w+')
print >>memo, 'StringIO'; print >>serr, 'stderr'; print >>file, 'file'
print >>None, value()
上述代码执⾏后,屏显为"serr"和"StringIO"(两⾏,注意顺序),⽂件内写⼊"file"。
可见,这种⽅式⾮常灵活和⽅便。缺点是不适⽤于输出语句较多的场景。
2.3 sys.stdout重定向
将⼀个可写对象(如file-like对象)赋给sys.stdout,可使随后的print语句输出⾄该对象。重定向结束后,应将sys.stdout恢复最初的缺省值,即标准输出。
简单⽰例如下:
import sys
savedStdout = sys.stdout #保存标准输出流
with open('', 'w+') as file:
sys.stdout = file #标准输出重定向⾄⽂件
print 'This message is for file!'
sys.stdout = savedStdout #恢复标准输出流
print 'This message is for screen!'
注意,IDLE中sys.stdout初值为PseudoOutputFile对象,与sys.__stdout__并不相同。为求通⽤,本例另⾏定义变量(savedStdout)保存sys.stdout,下⽂也将作此处理。此外,本例不适⽤于经由from sys import stdout导⼊的stdout对象。
以下将⾃定义多种具有write()⽅法的file-like对象,以满⾜不同需求:
class RedirectStdout: #import os, sys, cStringIO
def __init__(self):
self.savedStdout = sys.stdout
#外部的print语句将执⾏本write()⽅法,并由当前sys.stdout输出
def write(self, outStr):
#t.append(outStr)
def toCons(self): #标准输出重定向⾄控制台
sys.stdout = self.savedStdout #sys.__stdout__
def toMemo(self): #标准输出重定向⾄内存
sys.stdout = Obj
def toFile(self, file=''): #标准输出重定向⾄⽂件
self.fileObj = open(file, 'a+', 1) #改为⾏缓冲
sys.stdout = self.fileObj
def toMute(self): #抑制输出
self.nulObj = open(os.devnull, 'w')
sys.stdout = self.nulObj
def restore(self):
Obj.closed != True:
if self.fileObj.closed != True:
self.fileObj.close()
if self.nulObj.closed != True:
self.nulObj.close()
sys.stdout = self.savedStdout #sys.__stdout__
注意,toFile()⽅法中,open(name[, mode[, buffering]])调⽤选择⾏缓冲(⽆缓冲会影响性能)。这是为了观察中间写⼊过程,否则只有调⽤close()或flush()后输出才会写⼊⽂件。内部调⽤open()⽅法的缺点是不便于⽤户定制写⽂件规则,如模式(覆盖或追加)和缓冲(⾏或全缓冲)。
重定向效果如下:
redirObj = RedirectStdout()
sys.stdout = redirObj #本句会抑制"Let's begin!"输出
print "Let's begin!"
#屏显'Hello World!'和'I am xywang.'(两⾏)
#写⼊'How are you?'和"Can't complain."(两⾏)
os.system('echo Never redirect me!')  #控制台屏显'Never redirect me!'
print 'Pop up' #屏显
可见,执⾏toXXXX()语句后,标准输出流将被重定向到XXXX。此外,toMute()和toMemo()的效果类似,均可抑制输出。
使⽤某对象替换sys.stdout时,尽量确保该对象接近⽂件对象,尤其是涉及第三⽅库时(该库可能使⽤sys.stdout的其他⽅法)。此外,本节替换sys.stdout的代码实现并不影响由os.popen()、os.system()或os.
exec*()系列⽅法所创建进程的标准I/O流。
2.4 上下⽂管理器(Context Manager)
本节严格意义上并⾮新的重定向⽅式,⽽是利⽤Pyhton上下⽂管理器优化上节的代码实现。借助于上下⽂管理器语法,可不必向重定向使⽤者暴露sys.stdout。
⾸先考虑输出抑制,基于上下⽂管理器语法实现如下:
import sys, cStringIO, contextlib
class DummyFile:
def write(self, outStr): pass
@tmanager
def MuteStdout():
savedStdout = sys.stdout
sys.stdout = cStringIO.StringIO() #DummyFile()
try:
yield
except Exception: #捕获到错误时,屏显被抑制的输出(该处理并⾮必需)
content, sys.stdout = sys.stdout, savedStdout
value()#; raise
#finally:
sys.stdout = savedStdout
使⽤⽰例如下:
with MuteStdout():
print "I'll show up when <raise> is executed!" #不屏显不写⼊
raise #屏显上句
print "I'm hiding myself somewhere:)" #不屏显
再考虑更通⽤的输出重定向:
import os, sys
from contextlib import contextmanager
@contextmanager
def RedirectStdout(newStdout):
并输出
savedStdout, sys.stdout = sys.stdout, newStdout
try:
yield
finally:
sys.stdout = savedStdout
使⽤⽰例如下:
def Greeting(): print 'Hello, boss!'
with open('', "w+") as file:
print "I'm writing "  #屏显
with RedirectStdout(file):
print 'I hope this letter finds you well!' #写⼊⽂件
print 'Check your mailbox.'  #屏显
with open(os.devnull, "w+") as file, RedirectStdout(file):
Greeting()          #不屏显不写⼊
print 'I deserve a pay raise:)' #不屏显不写⼊
print 'Did you hear what I said?'  #屏显
可见,with内嵌块⾥的函数和print语句输出均被重定向。注意,上述⽰例不是线程安全的,主要适⽤于单线程。
当函数被频繁调⽤时,建议使⽤装饰器包装该函数。这样,仅需修改该函数定义,⽽⽆需在每次调⽤该函数时使⽤with语句包裹。⽰例如下:
import sys, cStringIO, functools
def MuteStdout(retCache=False):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
savedStdout = sys.stdout
sys.stdout = cStringIO.StringIO()
try:
ret = func(*args, **kwargs)
if retCache == True:
ret = value().strip()
finally:
sys.stdout = savedStdout
return ret
return wrapper
return decorator
若装饰器MuteStdout的参数retCache为真,外部调⽤func()函数时将返回该函数内部print输出的内容(可供屏显);若retCache为假,外部调⽤func()函数时将返回该函数的返回值(抑制输出)。
MuteStdout装饰器使⽤⽰例如下:
@MuteStdout(True)
def Exclaim(): print 'I am proud of myself!'
@MuteStdout()
def Mumble(): print 'I '; return 'sad'
print Exclaim(), Exclaim.__name__ #屏显'I am proud of myself! Exclaim'
print Mumble(), Mumble.__name__  #屏显'sad Mumble'
在所有线程中,被装饰函数执⾏期间,sys.stdout都会被MuteStdout装饰器劫持。⽽且,函数⼀经装饰便⽆法移除装饰。因此,使⽤该装饰器时应慎重考虑场景。
接着,考虑创建RedirectStdout装饰器:
def RedirectStdout(newStdout=sys.stdout):
def decorator(func):
def wrapper(*args,**kwargs):
savedStdout, sys.stdout = sys.stdout, newStdout
try:
return func(*args, **kwargs)
finally:
sys.stdout = savedStdout
return wrapper
return decorator
使⽤⽰例如下:
file = open('', "w+")
@RedirectStdout(file)
def FunNoArg(): print 'No argument.'
@RedirectStdout(file)
def FunOneArg(a): print 'One argument:', a
def FunTwoArg(a, b): print 'Two arguments: %s, %s' %(a,b)
FunNoArg()  #写⽂件'No argument.'
FunOneArg(1984) #写⽂件'One argument: 1984'
RedirectStdout()(FunTwoArg)(10,29) #屏显'Two arguments: 10, 29'
print FunNoArg.__name__      #屏显'wrapper'(应显⽰'FunNoArg')
file.close()
注意FunTwoArg()函数的定义和调⽤与其他函数的不同,这是两种等效的语法。此外,RedirectStdout装饰器的最内层函
数wrapper()未使⽤"functools.wraps(func)"修饰,会丢失被装饰函数原有的特殊属性(如函数名、⽂档字符串等)。
2.5 logging模块重定向
对于代码量较⼤的⼯程,建议使⽤logging模块进⾏输出。该模块是线程安全的,可将⽇志信息输出到控制台、写⼊⽂件、使⽤TCP/UDP协议发送到⽹络等等。
默认情况下logging模块将⽇志输出到控制台(标准出错),且只显⽰⼤于或等于设置的⽇志级别的⽇志。⽇志级别由⾼到低
为CRITICAL > ERROR > WARNING > INFO > DEBUG > NOTSET,默认级别为WARNING。
以下⽰例将⽇志信息分别输出到控制台和写⼊⽂件:
import logging
logging.basicConfig(level = logging.DEBUG,
format = '%(asctime)s [%(levelname)s] at %(filename)s,%(lineno)d: %(message)s',
datefmt = '%Y-%m-%d(%a)%H:%M:%S',
filename = '',
filemode = 'w')
#将⼤于或等于INFO级别的⽇志信息输出到StreamHandler(默认为标准错误)
console = logging.StreamHandler()
console.setLevel(logging.INFO)
formatter = logging.Formatter('[%(levelname)-8s] %(message)s') #屏显实时查看,⽆需时间
console.setFormatter(formatter)
logging.debug('gubed'); logging.info('ofni'); itical('lacitirc')
通过对多个handler设置不同的level参数,可将不同的⽇志内容输⼊到不同的地⽅。本例使⽤在logging模块内置的StreamHandler(和FileHandler),运⾏后屏幕上显⽰:
[INFO  ] ofni
[CRITICAL] lacitirc
<⽂件内容则为:
2016-05-13(Fri)17:10:53 [DEBUG] at test.py,25: gubed
2016-05-13(Fri)17:10:53 [INFO] at test.py,25: ofni
2016-05-13(Fri)17:10:53 [CRITICAL] at test.py,25: lacitirc
除直接在程序中设置Logger、Handler、Formatter等外,还可将这些信息写⼊配置⽂件。⽰例如下:

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。