Heim  >  Artikel  >  Backend-Entwicklung  >  Python-Standardausgabeumleitungsmethode

Python-Standardausgabeumleitungsmethode

高洛峰
高洛峰Original
2017-02-25 13:11:213043Durchsuche

1. Hintergrund

In Python entsprechen die Dateiobjekte sys.stdin, sys.stdout und sys.stderr der Standardeingabe, der Standardausgabe und dem Standard des Interpreter bzw. Fehlerfluss. Beim Programmstart werden die Anfangswerte dieser Objekte durch sys.__stdin__, sys.__stdout__ und sys.__stderr__ gespeichert, sodass sie bei der Finalisierung zur Wiederherstellung der Standard-Stream-Objekte verwendet werden können.

IDLE (Python-GUI) im Windows-System wird von pythonw.exe ausgeführt und die GUI verfügt nicht über eine Konsole. Daher ersetzt IDLE das Standardausgabehandle durch ein spezielles PseudoOutputFile-Objekt, sodass die Skriptausgabe an das IDLE-Terminalfenster (Shell) umgeleitet wird. Dies kann zu einigen seltsamen Problemen führen, wie zum Beispiel:

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 &#39;<stdin>&#39;, mode &#39;r&#39; at 0x00FED020>
<open file &#39;<stdout>&#39;, mode &#39;w&#39; at 0x00FED078>
<open file &#39;<stderr>&#39;, mode &#39;w&#39; at 0x00FED0D0>
>>>

Es kann festgestellt werden, dass die Werte von sys.__stdout__ und sys.stdout sind nicht dasselbe. Wenn der obige Code unter einem normalen Python-Interpreter ausgeführt wird (z. B. über eine Windows-Konsole), sind die beiden Werte gleich.

Wenn die Druckanweisung nicht mit einem Komma endet, wird automatisch ein Zeilenumbruchzeichen (Zeilenvorschub) an das Ende der Ausgabezeichenfolge angehängt, andernfalls wird das zusätzliche Zeilenumbruchzeichen durch ein Leerzeichen ersetzt. Die print-Anweisung schreibt standardmäßig in den Standardausgabestream und kann auch in eine Datei oder ein anderes beschreibbares Objekt (alle Objekte, die eine Schreibmethode bereitstellen) umgeleitet werden. Auf diese Weise können Sie prägnante Druckaussagen statt umständlicher object.write('hello'+'n')-Schreibweise verwenden.

Wie oben zu sehen ist, entspricht der Aufruf von print obj zum Drucken eines Objekts in Python standardmäßig dem Aufruf von sys.stdout.write(obj+'n')

. Beispiele sind wie folgt:

>>> import sys
>>> print &#39;Hello World&#39;
Hello World
>>> sys.stdout.write(&#39;Hello World&#39;)
Hello World

2. Umleitungsmethoden

In diesem Abschnitt wird das häufig verwendete Python vorgestellt Standardmethoden zur Ausgabeumleitung. Jede dieser Methoden hat ihre eigenen Vor- und Nachteile und eignet sich für unterschiedliche Szenarien.

2.1 Konsolenumleitung

Die einfachste und gebräuchlichste Möglichkeit, die Ausgabe umzuleiten, ist die Verwendung von Konsolenbefehlen. Diese Umleitung erfolgt durch die Konsole und hat nichts mit Python selbst zu tun.

Sowohl die Windows-Eingabeaufforderung (cmd.exe) als auch die Linux-Shell (Bash usw.) leiten die Ausgabe über „>“ oder „>>“ um. Dabei bedeutet „>“ das Überschreiben von Inhalten und „>>“ das Anhängen von Inhalten. Ebenso leitet „2>“ den Standardfehler um. Durch die Umleitung auf „nul“ (Windows) oder „/dev/null“ (Linux) wird die Ausgabe unterdrückt und weder auf dem Bildschirm angezeigt noch auf der Festplatte gespeichert.

Leiten Sie am Beispiel der Windows-Eingabeaufforderung die Python-Skriptausgabe in eine Datei um (Leerzeilen zwischen Befehlen wurden gelöscht, um die Länge zu verkürzen):

E:\>echo print &#39;hello&#39; > test.py
E:\>test.py > out.txt
E:\>type out.txt
hello
E:\>test.py >> out.txt
E:\>type out.txt
hello
hello
E:\>test.py > nul

Hinweis: Wenn Sie ein Python-Skript in der Windows-Eingabeaufforderung ausführen, muss die Befehlszeile nicht mit „python“ beginnen, das System ruft automatisch den Python-Interpreter auf das Skriptsuffix. Darüber hinaus kann der Typbefehl den Inhalt einer Textdatei direkt anzeigen, ähnlich dem Befehl cat in Linux-Systemen.

Beim Ausführen eines Python-Skripts in der Linux-Shell sollte die Befehlszeile mit „python“ beginnen. Zusätzlich zur Umleitung „>“ oder „>>“ kann auch der Befehl tee verwendet werden. Dieser Befehl kann den Inhalt auf dem Terminalbildschirm und in (mehreren) Dateien gleichzeitig ausgeben. Die Option „-a“ bedeutet „Schreiben anhängen“, andernfalls wird das Schreiben überschrieben. Ein Beispiel ist wie folgt (echo $SHELL oder echo

zeigt die aktuell verwendete Shell an):

[wangxiaoyuan_@localhost ~]$ echo $SHELL
/bin/bash
[wangxiaoyuan_@localhost ~]$ python -c "print &#39;hello&#39;"
hello
[wangxiaoyuan_@localhost ~]$ python -c "print &#39;hello&#39;" > out.txt
[wangxiaoyuan_@localhost ~]$ cat out.txt
hello
[wangxiaoyuan_@localhost ~]$ python -c "print &#39;world&#39;" >> out.txt
[wangxiaoyuan_@localhost ~]$ cat out.txt             
hello
world
[wangxiaoyuan_@localhost ~]$ python -c "print &#39;I am&#39;" | tee out.txt
I am
[wangxiaoyuan_@localhost ~]$ python -c "print &#39;xywang&#39;" | tee -a out.txt
xywang
[wangxiaoyuan_@localhost ~]$ cat out.txt
I am
xywang
[wangxiaoyuan_@localhost ~]$ python -c "print &#39;hello&#39;" > /dev/null
[wangxiaoyuan_@localhost ~]$

Wenn Sie nur die Skriptausgabe speichern möchten in eine Datei kopieren oder direkt die Protokollerfassungsfunktion des Sitzungsfensters verwenden.

Beachten Sie, dass die Auswirkungen der Konsolenumleitung global sind und nur für relativ einfache Ausgabeaufgaben gelten.

2.2 print >> Umleitung

print obj >> exprDiese Methode basiert auf der erweiterten Form der print-Anweisung, also „obj“. Unter diesen ist expr ein dateiähnliches Objekt (insbesondere eines, das eine Schreibmethode bereitstellt), und wenn es None ist, entspricht es der Standardausgabe (sys.stdout).

wird in dieses Dateiobjekt ausgegeben.

Beispiele sind wie folgt:

memo = cStringIO.StringIO(); serr = sys.stderr; file = open(&#39;out.txt&#39;, &#39;w+&#39;)
print >>memo, &#39;StringIO&#39;; print >>serr, &#39;stderr&#39;; print >>file, &#39;file&#39;
print >>None, memo.getvalue()

Nachdem der obige Code ausgeführt wurde, werden auf dem Bildschirm „serr“ und „StringIO“ angezeigt ( zwei Zeilen, bitte Reihenfolge beachten), schreiben Sie „file“ in die out.txt-Datei.

Es ist ersichtlich, dass diese Methode sehr flexibel und bequem ist. Der Nachteil besteht darin, dass es nicht für Szenarien mit vielen Ausgabeanweisungen geeignet ist.

2.3 sys.stdout-Umleitung

Das Zuweisen eines beschreibbaren Objekts (z. B. eines dateiähnlichen Objekts) zu sys.stdout ermöglicht die Ausgabe nachfolgender Druckanweisungen dieses Objekt. Nach Abschluss der Umleitung sollte sys.stdout auf seinen ursprünglichen Standardwert, der Standardausgabe, wiederhergestellt werden.

Ein einfaches Beispiel lautet wie folgt:

import sys
savedStdout = sys.stdout #保存标准输出流
with open(&#39;out.txt&#39;, &#39;w+&#39;) as file:
  sys.stdout = file #标准输出重定向至文件
  print &#39;This message is for file!&#39;

sys.stdout = savedStdout #恢复标准输出流
print &#39;This message is for screen!&#39;

sys.stdoutBeachten Sie, dass der Anfangswert von sys.__stdout__ in IDLE das PseudoOutputFile-Objekt ist. was sich von sys.stdoutgleich unterscheidet. Der Allgemeinheit halber definiert dieses Beispiel eine separate Variable (savedStdout) zum Speichern von from sys import stdout, was im Folgenden ebenfalls durchgeführt wird. Darüber hinaus funktioniert dieses Beispiel nicht mit über

importierten Stdout-Objekten.

write()Im Folgenden werden verschiedene dateiähnliche Objekte mit

Methoden angepasst, um unterschiedliche Anforderungen zu erfüllen:

class RedirectStdout: #import os, sys, cStringIO
  def __init__(self):
    self.content = &#39;&#39;
    self.savedStdout = sys.stdout
    self.memObj, self.fileObj, self.nulObj = None, None, None

  #外部的print语句将执行本write()方法,并由当前sys.stdout输出
  def write(self, outStr):
    #self.content.append(outStr)
    self.content += outStr

  def toCons(self): #标准输出重定向至控制台
    sys.stdout = self.savedStdout #sys.__stdout__

  def toMemo(self): #标准输出重定向至内存
    self.memObj = cStringIO.StringIO()
    sys.stdout = self.memObj

  def toFile(self, file=&#39;out.txt&#39;): #标准输出重定向至文件
    self.fileObj = open(file, &#39;a+&#39;, 1) #改为行缓冲
    sys.stdout = self.fileObj
  
  def toMute(self): #抑制输出
    self.nulObj = open(os.devnull, &#39;w&#39;)
    sys.stdout = self.nulObj
    
  def restore(self):
    self.content = &#39;&#39;
    if self.memObj.closed != True:
      self.memObj.close()
    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&#39;s begin!"输出
print "Let&#39;s begin!"

#屏显&#39;Hello World!&#39;和&#39;I am xywang.&#39;(两行)
redirObj.toCons(); print &#39;Hello World!&#39;; print &#39;I am xywang.&#39;
#写入&#39;How are you?&#39;和"Can&#39;t complain."(两行)
redirObj.toFile(); print &#39;How are you?&#39;; print "Can&#39;t complain."
redirObj.toCons(); print "What&#39;up?"   #屏显
redirObj.toMute(); print &#39;<Silence>&#39;  #无屏显或写入
os.system(&#39;echo Never redirect me!&#39;)  #控制台屏显&#39;Never redirect me!&#39;
redirObj.toMemo(); print &#39;What a pity!&#39; #无屏显或写入
redirObj.toCons(); print &#39;Hello?&#39;    #屏显
redirObj.toFile(); print "Oh, xywang can&#39;t hear me" #该串写入文件
redirObj.restore()

print &#39;Pop up&#39; #屏显

可见,执行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

@contextlib.contextmanager
def MuteStdout():
  savedStdout = sys.stdout
  sys.stdout = cStringIO.StringIO() #DummyFile()
  try:
    yield
  except Exception: #捕获到错误时,屏显被抑制的输出(该处理并非必需)
    content, sys.stdout = sys.stdout, savedStdout
    print content.getvalue()#; raise
  #finally:
  sys.stdout = savedStdout

使用示例如下:

with MuteStdout():
  print "I&#39;ll show up when <raise> is executed!" #不屏显不写入
  raise #屏显上句
  print "I&#39;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 &#39;Hello, boss!&#39;

with open(&#39;out.txt&#39;, "w+") as file:
  print "I&#39;m writing to you..."  #屏显
  with RedirectStdout(file):
    print &#39;I hope this letter finds you well!&#39; #写入文件
  print &#39;Check your mailbox.&#39;   #屏显

with open(os.devnull, "w+") as file, RedirectStdout(file):
  Greeting()           #不屏显不写入
  print &#39;I deserve a pay raise:)&#39; #不屏显不写入
print &#39;Did you hear what I said?&#39;  #屏显

可见,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 = sys.stdout.getvalue().strip()
      finally:
        sys.stdout = savedStdout
      return ret
    return wrapper
  return decorator

若装饰器MuteStdout的参数retCache为真,外部调用func()函数时将返回该函数内部print输出的内容(可供屏显);若retCache为假,外部调用func()函数时将返回该函数的返回值(抑制输出)。

MuteStdout装饰器使用示例如下:

@MuteStdout(True)
def Exclaim(): print &#39;I am proud of myself!&#39;

@MuteStdout()
def Mumble(): print &#39;I lack confidence...&#39;; return &#39;sad&#39;

print Exclaim(), Exclaim.__name__ #屏显&#39;I am proud of myself! Exclaim&#39;
print Mumble(), Mumble.__name__  #屏显&#39;sad Mumble&#39;

在所有线程中,被装饰函数执行期间,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(&#39;out.txt&#39;, "w+")
@RedirectStdout(file)
def FunNoArg(): print &#39;No argument.&#39;

@RedirectStdout(file)
def FunOneArg(a): print &#39;One argument:&#39;, a

def FunTwoArg(a, b): print &#39;Two arguments: %s, %s&#39; %(a,b)

FunNoArg()   #写文件&#39;No argument.&#39;
FunOneArg(1984) #写文件&#39;One argument: 1984&#39;
RedirectStdout()(FunTwoArg)(10,29) #屏显&#39;Two arguments: 10, 29&#39;
print FunNoArg.__name__      #屏显&#39;wrapper&#39;(应显示&#39;FunNoArg&#39;)

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 = &#39;%(asctime)s [%(levelname)s] at %(filename)s,%(lineno)d: %(message)s&#39;,
  datefmt = &#39;%Y-%m-%d(%a)%H:%M:%S&#39;,
  filename = &#39;out.txt&#39;,
  filemode = &#39;w&#39;)

 
#将大于或等于INFO级别的日志信息输出到StreamHandler(默认为标准错误)
console = logging.StreamHandler()
console.setLevel(logging.INFO) 
formatter = logging.Formatter(&#39;[%(levelname)-8s] %(message)s&#39;) #屏显实时查看,无需时间
console.setFormatter(formatter)
logging.getLogger().addHandler(console)

logging.debug(&#39;gubed&#39;); logging.info(&#39;ofni&#39;); logging.critical(&#39;lacitirc&#39;)

通过对多个handler设置不同的level参数,可将不同的日志内容输入到不同的地方。本例使用在logging模块内置的StreamHandler(和FileHandler),运行后屏幕上显示:

[INFO  ] ofni
[CRITICAL] lacitirc

out.txt文件内容则为:

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等外,还可将这些信息写入配置文件。示例如下:

#logger.conf
###############Logger###############
[loggers]
keys=root,Logger2F,Logger2CF

[logger_root]
level=DEBUG
handlers=hWholeConsole

[logger_Logger2F]
handlers=hWholeFile
qualname=Logger2F
propagate=0

[logger_Logger2CF]
handlers=hPartialConsole,hPartialFile
qualname=Logger2CF
propagate=0

###############Handler###############
[handlers]
keys=hWholeConsole,hPartialConsole,hWholeFile,hPartialFile

[handler_hWholeConsole]
class=StreamHandler
level=DEBUG
formatter=simpFormatter
args=(sys.stdout,)

[handler_hPartialConsole]
class=StreamHandler
level=INFO
formatter=simpFormatter
args=(sys.stderr,)

[handler_hWholeFile]
class=FileHandler
level=DEBUG
formatter=timeFormatter
args=(&#39;out.txt&#39;, &#39;a&#39;)

[handler_hPartialFile]
class=FileHandler
level=WARNING
formatter=timeFormatter
args=(&#39;out.txt&#39;, &#39;w&#39;)

###############Formatter###############
[formatters]
keys=simpFormatter,timeFormatter

[formatter_simpFormatter]
format=[%(levelname)s] at %(filename)s,%(lineno)d: %(message)s

[formatter_timeFormatter]
format=%(asctime)s [%(levelname)s] at %(filename)s,%(lineno)d: %(message)s
datefmt=%Y-%m-%d(%a)%H:%M:%S

此处共创建三个Logger:root,将所有日志输出至控制台;Logger2F,将所有日志写入文件;Logger2CF,将级别大于或等于INFO的日志输出至控制台,将级别大于或等于WARNING的日志写入文件。

程序以如下方式解析配置文件和重定向输出:

import logging, logging.config

logging.config.fileConfig("logger.conf")

logger = logging.getLogger("Logger2CF")
logger.debug(&#39;gubed&#39;); logger.info(&#39;ofni&#39;); logger.warn(&#39;nraw&#39;)
logger.error(&#39;rorre&#39;); logger.critical(&#39;lacitirc&#39;)

logger1 = logging.getLogger("Logger2F")
logger1.debug(&#39;GUBED&#39;); logger1.critical(&#39;LACITIRC&#39;)

logger2 = logging.getLogger()
logger2.debug(&#39;gUbEd&#39;); logger2.critical(&#39;lAcItIrC&#39;)

运行后屏幕上显示:

[INFO] at test.py,7: ofni
[WARNING] at test.py,7: nraw
[ERROR] at test.py,8: rorre
[CRITICAL] at test.py,8: lacitirc
[DEBUG] at test.py,14: gUbEd
[CRITICAL] at test.py,14: lAcItIrC

out.txt文件内容则为:

2016-05-13(Fri)20:31:21 [WARNING] at test.py,7: nraw
2016-05-13(Fri)20:31:21 [ERROR] at test.py,8: rorre
2016-05-13(Fri)20:31:21 [CRITICAL] at test.py,8: lacitirc
2016-05-13(Fri)20:31:21 [DEBUG] at test.py,11: GUBED
2016-05-13(Fri)20:31:21 [CRITICAL] at test.py,11: LACITIRC

三. 总结

以上就是关于Python标准输出的重定向方式的全部内容,希望对学习python的朋友们能有所帮助,如果有疑问欢迎大家留言讨论。

Weitere Artikel zu Python-Standardausgabeumleitungsmethoden finden Sie auf der chinesischen PHP-Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn