在Python 3.4+中有一个contextlib.redirect_stdout()
函数:
from contextlib import redirect_stdout
with open('help.txt', 'w') as f:
with redirect_stdout(f):
print('it now prints to `help.text`')
它类似于:
import sys
from contextlib import contextmanager
@contextmanager
def redirect_stdout(new_target):
old_target, sys.stdout = sys.stdout, new_target
try:
yield new_target
finally:
sys.stdout = old_target
可以在早期Python版本上使用。后者版本不可重用,如果需要可以将其制作成可重用的。
它不会在文件描述符级别上重定向stdout,例如:
import os
from contextlib import redirect_stdout
stdout_fd = sys.stdout.fileno()
with open('output.txt', 'w') as f, redirect_stdout(f):
print('redirected to a file')
os.write(stdout_fd, b'not redirected')
os.system('echo this also is not redirected')
b'not redirected'
和'echo this also is not redirected'
未被重定向到output.txt
文件。
要在文件描述符级别上重定向,可以使用os.dup2()
:
import os
import sys
from contextlib import contextmanager
def fileno(file_or_fd):
fd = getattr(file_or_fd, 'fileno', lambda: file_or_fd)()
if not isinstance(fd, int):
raise ValueError("Expected a file (`.fileno()`) or a file descriptor")
return fd
@contextmanager
def stdout_redirected(to=os.devnull, stdout=None):
if stdout is None:
stdout = sys.stdout
stdout_fd = fileno(stdout)
with os.fdopen(os.dup(stdout_fd), 'wb') as copied:
stdout.flush()
try:
os.dup2(fileno(to), stdout_fd)
except ValueError:
with open(to, 'wb') as to_file:
os.dup2(to_file.fileno(), stdout_fd)
try:
yield stdout
finally:
stdout.flush()
os.dup2(copied.fileno(), stdout_fd)
如果使用stdout_redirected()
而不是redirect_stdout()
,则现在相同的示例可以正常工作:
import os
import sys
stdout_fd = sys.stdout.fileno()
with open('output.txt', 'w') as f, stdout_redirected(f):
print('redirected to a file')
os.write(stdout_fd, b'it is redirected now\n')
os.system('echo this is also redirected')
print('this is goes back to stdout')
只要使用stdout_redirected()
上下文管理器,之前在标准输出(stdout)打印的内容就会被重定向到output.txt
文件中。
注意:在Python 3中,由于I/O是直接实现在read()
/write()
系统调用上的,所以stdout.flush()
不会刷新C stdio缓冲区。如果某个C扩展程序使用了基于stdio的I/O,您可以显式调用libc.fflush(None)
来刷新所有打开的C stdio输出流:
try:
import ctypes
from ctypes.util import find_library
except ImportError:
libc = None
else:
try:
libc = ctypes.cdll.msvcrt
except OSError:
libc = ctypes.cdll.LoadLibrary(find_library('c'))
def flush(stream):
try:
libc.fflush(None)
stream.flush()
except (AttributeError, ValueError, IOError):
pass
您可以使用 stdout
参数来重定向其他流,而不仅仅是 sys.stdout
,例如将 sys.stderr
和 sys.stdout
合并:
def merged_stderr_stdout():
return stdout_redirected(to=sys.stdout, stdout=sys.stderr)
示例:
from __future__ import print_function
import sys
with merged_stderr_stdout():
print('this is printed on stdout')
print('this is also printed on stdout', file=sys.stderr)
注意:
stdout_redirected()
混合了缓冲的I/O(通常是
sys.stdout
)和未缓冲的I/O(直接操作文件描述符)。请注意,可能会出现
缓冲问题。
要回答你的编辑:您可以使用
python-daemon
来将脚本设置为守护进程,并使用
logging
模块(如
@erikb85建议的那样),而不是使用
print
语句仅仅重定向标准输出流,用于您现在使用
nohup
运行的长时间运行的Python脚本。
script.p > file
。 - Falmarri