装饰\委派File对象以添加功能
我正在编写一个小的Python脚本,该脚本使用该subprocess
模块和一个辅助函数来执行一些Shell命令:
import subprocess as sp
def run(command, description):
"""Runs a command in a formatted manner. Returns its return code."""
start=datetime.datetime.now()
sys.stderr.write('%-65s' % description)
s=sp.Popen(command, shell=True, stderr=sp.PIPE, stdout=sp.PIPE)
out,err=s.communicate()
end=datetime.datetime.now()
duration=end-start
status='Done' if s.returncode==0 else 'Failed'
print '%s (%d seconds)' % (status, duration.seconds)
以下几行显示标准输出和错误:
s=sp.Popen(command, shell=True, stderr=sp.PIPE, stdout=sp.PIPE)
out,err=s.communicate()
如您所见,未使用stdout和stderr。假设我想以格式化的方式将输出和错误消息写入日志文件,例如:
[STDOUT: 2011-01-17 14:53:55] <message>
[STDERR: 2011-01-17 14:53:56] <message>
我的问题是,最Python化的方法是什么?我想到了三个选择:
- 继承文件对象并覆盖
write
方法。 - 使用实现的Delegate类
write
。 - 以
PIPE
某种方式连接到自身。
更新:参考测试脚本
我正在使用此脚本检查结果,该脚本另存为test.py
:
#!/usr/bin/python
import sys
sys.stdout.write('OUT\n')
sys.stdout.flush()
sys.stderr.write('ERR\n')
sys.stderr.flush()
有任何想法吗?
-
1和2是合理的解决方案,但仅重写write()是不够的。
问题是Popen需要将文件句柄附加到进程,因此Python文件对象不起作用,它们必须是OS级别的。要解决这个问题,您必须拥有一个具有os级文件句柄的Python对象。我能想到的解决此问题的唯一方法是使用管道,因此您要写入一个os级文件句柄。但是,然后您需要另一个线程来坐下来并轮询该管道以供读取内容,以便可以对其进行记录。(因此,严格来说,这是2的实现,因为它委派了日志记录)。
说完了:
import io import logging import os import select import subprocess import time import threading LOG_FILENAME = 'output.log' logging.basicConfig(filename=LOG_FILENAME,level=logging.DEBUG) class StreamLogger(io.IOBase): def __init__(self, level): self.level = level self.pipe = os.pipe() self.thread = threading.Thread(target=self._flusher) self.thread.start() def _flusher(self): self._run = True buf = b'' while self._run: for fh in select.select([self.pipe[0]], [], [], 0)[0]: buf += os.read(fh, 1024) while b'\n' in buf: data, buf = buf.split(b'\n', 1) self.write(data.decode()) time.sleep(1) self._run = None def write(self, data): return logging.log(self.level, data) def fileno(self): return self.pipe[1] def close(self): if self._run: self._run = False while self._run is not None: time.sleep(1) os.close(self.pipe[0]) os.close(self.pipe[1])
这样,该类将启动os级管道,Popen可以将stdin / out /
error附加到该子进程。它还启动一个线程,该线程每秒轮询该管道的另一端以进行记录,然后将其记录到日志记录模块中。此类可能为了实现完整性而应实现更多功能,但无论如何在这种情况下仍然有效。
示例代码:
with StreamLogger(logging.INFO) as out: with StreamLogger(logging.ERROR) as err: subprocess.Popen("ls", stdout=out, stderr=err, shell=True)
output.log最终如下所示:
INFO:root:output.log INFO:root:streamlogger.py INFO:root:and INFO:root:so INFO:root:on
经过Python 2.6、2.7和3.1测试。
我认为1和3的任何实现都需要使用类似的技术。它有点涉及,但是除非您可以正确地使Popen命令本身记录日志,否则我没有更好的主意)。