![](/img/trans.png)
[英]Python: only print stderr, not stdout, from subprocess.call()?
[英]Suppress stdout / stderr print from Python functions
我有一個 Python 腳本,它使用我的雇主提供的一些封閉式 Python 函數(即我無法編輯這些函數)。 當我調用這些函數時,它們將輸出打印到我想抑制的 linux 終端。 我試過通過以下方式重定向標准輸出/標准錯誤;
orig_out = sys.stdout
sys.stdout = StringIO()
rogue_function()
sys.stdout = orig_out
但這無法捕獲輸出。 我認為我通過 Python 調用的函數(上面的 rogue_function() )實際上是編譯的 C 代碼的包裝器,它們實際上是在進行打印。
有誰知道我可以通過函數(以及函數調用的任何子函數)對傳遞給 stdout / stderr 的任何打印進行“深度捕獲”的方法?
更新:
我最終采用了下面所選答案中概述的方法,並編寫了一個上下文管理器來抑制 stdout 和 stderr:
# Define a context manager to suppress stdout and stderr.
class suppress_stdout_stderr(object):
'''
A context manager for doing a "deep suppression" of stdout and stderr in
Python, i.e. will suppress all print, even if the print originates in a
compiled C/Fortran sub-function.
This will not suppress raised exceptions, since exceptions are printed
to stderr just before a script exits, and after the context manager has
exited (at least, I think that is why it lets exceptions through).
'''
def __init__(self):
# Open a pair of null files
self.null_fds = [os.open(os.devnull,os.O_RDWR) for x in range(2)]
# Save the actual stdout (1) and stderr (2) file descriptors.
self.save_fds = [os.dup(1), os.dup(2)]
def __enter__(self):
# Assign the null pointers to stdout and stderr.
os.dup2(self.null_fds[0],1)
os.dup2(self.null_fds[1],2)
def __exit__(self, *_):
# Re-assign the real stdout/stderr back to (1) and (2)
os.dup2(self.save_fds[0],1)
os.dup2(self.save_fds[1],2)
# Close all file descriptors
for fd in self.null_fds + self.save_fds:
os.close(fd)
要使用它,您只需:
with suppress_stdout_stderr():
rogue_function()
這工作“相當好”。 它確實抑制了使我的腳本混亂的流氓函數的打印輸出。 我在測試它時注意到它允許通過引發的異常以及一些記錄器打印,我不完全清楚為什么。 我認為這與這些消息何時發送到 stdout / stderr 有關(我認為它發生在我的上下文管理器退出之后)。 如果有人可以證實這一點,我很想聽聽細節......
從 python 3.5 開始,我們可以使用contextlib
中的內置插件(即redirect_stdout
和redirect_stderr
)以最少的工作完成此操作。 我們只需要將這兩個內置上下文管理器組合到我們的自定義上下文管理器中,這可以使用Martijn 在此處的回答中的 nice 模式輕松完成。 將兩個輸出重定向到os.devnull
應該足夠安全和可移植。
from contextlib import contextmanager,redirect_stderr,redirect_stdout
from os import devnull
@contextmanager
def suppress_stdout_stderr():
"""A context manager that redirects stdout and stderr to devnull"""
with open(devnull, 'w') as fnull:
with redirect_stderr(fnull) as err, redirect_stdout(fnull) as out:
yield (err, out)
請注意,抑制stderr
仍然會在出現問題時為您提供完整的回溯,這是一件好事:
import sys
def rogue_function():
print('spam to stdout')
print('important warning', file=sys.stderr)
1 + 'a'
return 42
with suppress_stdout_stderr():
rogue_function()
當運行上面只打印
Traceback (most recent call last):
File "tmp.py", line 20, in <module>
rogue_function()
File "foo.py", line 16, in rogue_function
1 + 'a'
TypeError: unsupported operand type(s) for +: 'int' and 'str'
到終端。 未處理的異常永遠不應被忽視。
這種方法(通過相關的側邊欄找到)可能會奏效。 它在 sys.stdout 等中重新分配文件描述符,而不僅僅是包裝器。
python 3.6 工作版,經過百萬次壓制測試,無任何錯誤
import os
import sys
class suppress_stdout_stderr(object):
def __enter__(self):
self.outnull_file = open(os.devnull, 'w')
self.errnull_file = open(os.devnull, 'w')
self.old_stdout_fileno_undup = sys.stdout.fileno()
self.old_stderr_fileno_undup = sys.stderr.fileno()
self.old_stdout_fileno = os.dup ( sys.stdout.fileno() )
self.old_stderr_fileno = os.dup ( sys.stderr.fileno() )
self.old_stdout = sys.stdout
self.old_stderr = sys.stderr
os.dup2 ( self.outnull_file.fileno(), self.old_stdout_fileno_undup )
os.dup2 ( self.errnull_file.fileno(), self.old_stderr_fileno_undup )
sys.stdout = self.outnull_file
sys.stderr = self.errnull_file
return self
def __exit__(self, *_):
sys.stdout = self.old_stdout
sys.stderr = self.old_stderr
os.dup2 ( self.old_stdout_fileno, self.old_stdout_fileno_undup )
os.dup2 ( self.old_stderr_fileno, self.old_stderr_fileno_undup )
os.close ( self.old_stdout_fileno )
os.close ( self.old_stderr_fileno )
self.outnull_file.close()
self.errnull_file.close()
您是否也嘗試重定向 stderr? 例如
sys.stdout = StringIO()
sys.stderr = StringIO()
foo(bar)
sys.stdout = sys.__stdout__ # These are provided by python
sys.stderr = sys.__stderr__
此外,使用 StringIO 可能會占用額外的內存。 您可以改用虛擬設備(例如http://coreygoldberg.blogspot.com/2009/05/python-redirect-or-turn-off-stdout-and.html )。
我的解決方案與您的類似,但使用contextlib
並且更短且更易於理解(恕我直言)。
import contextlib
@contextlib.contextmanager
def stdchannel_redirected(stdchannel, dest_filename):
"""
A context manager to temporarily redirect stdout or stderr
e.g.:
with stdchannel_redirected(sys.stderr, os.devnull):
if compiler.has_function('clock_gettime', libraries=['rt']):
libraries.append('rt')
"""
try:
oldstdchannel = os.dup(stdchannel.fileno())
dest_file = open(dest_filename, 'w')
os.dup2(dest_file.fileno(), stdchannel.fileno())
yield
finally:
if oldstdchannel is not None:
os.dup2(oldstdchannel, stdchannel.fileno())
if dest_file is not None:
dest_file.close()
我創建它的原因是在這篇博文中。 我覺得跟你的差不多。
我在setup.py
中這樣使用它:
with stdchannel_redirected(sys.stderr, os.devnull):
if compiler.has_function('clock_gettime', libraries=['rt']):
libraries.append('rt')
OP 並沒有真正要求,但我需要隱藏和存儲輸出,並且如下所示:
from io import StringIO
import sys
class Hider:
def __init__(self, channels=('stdout',)):
self._stomach = StringIO()
self._orig = {ch : None for ch in channels}
def __enter__(self):
for ch in self._orig:
self._orig[ch] = getattr(sys, ch)
setattr(sys, ch, self)
return self
def write(self, string):
self._stomach.write(string)
def flush(self):
pass
def autopsy(self):
return self._stomach.getvalue()
def __exit__(self, *args):
for ch in self._orig:
setattr(sys, ch, self._orig[ch])
用法:
with Hider() as h:
spammy_function()
result = h.autopsy()
(僅使用 Python 3 進行測試)
編輯:現在允許選擇stderr
, stdout
或兩者,如Hider([stdout, stderr])
我為此使用了裝飾器。 它保存sys.stdout
和sys.stderr
引用並使這些變量指向 null。 然后,在函數執行后檢索原始引用。 重要的是要注意 try/except 塊,即使在函數引發異常時,它也允許檢索原始引用。
def suppress_std(func):
def wrapper(*args, **kwargs):
stderr_tmp = sys.stderr
stdout_tmp = sys.stdout
null = open(os.devnull, 'w')
sys.stdout = null
sys.stderr = null
try:
result = func(*args, **kwargs)
sys.stderr = stderr_tmp
sys.stdout = stdout_tmp
return result
except:
sys.stderr = stderr_tmp
sys.stdout = stdout_tmp
raise
return wrapper
使用:
@suppress_std
def function_std_suppressed():
# code here
只需使用 Linux/Unix:
./myscript.py 2>/dev/null # gets rid of stderr
./myscript.py 2>/somewhere/myerror.log
如果你在基於 linux 的機器上運行這個腳本,你應該能夠:
$> ./runscript.py > output.txt
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.