如何记录Python交互式shell会话中发生的所有事情?

10

我想要实时访问解释器的输入、错误和标准输出。最好将这些信息写入文件,以便在输入每个解释器命令后轮询文件以查看更改。例如,给定一个解释器会话:

>>> 5 * 7
35
>>> print("Hello, world!")
Hello, world!
>>> "Hello, world!"
'Hello, world!'

我希望在日志文件中看到以下内容:

> 5 * 7
35
> print("Hello, world!")
Hello, world!
> "Hello, world!"
'Hello, world!'

格式并不重要。重要的是我能够搜索文件以触发会话期间的交互事件。

到目前为止,我尝试过的方法是:

Python的code模块允许我创建一个InteractiveConsole对象,该对象的raw_input方法可以重新定义为记录到一个文件中,如下所示:

import code
class LoggedConsole(code.InteractiveConsole):
  def __init__(self, locals):
    super(LoggedConsole, self).__init__(locals)
    self.file = open('consolelog.dat', 'a')

  def __del__(self):
    self.file.close()

  def raw_input(self, prompt=""):
    data = input(prompt)
    self.file.write(data+'\n')
    return data

此外,InteractiveConsole 使用内置的 write 方法记录错误,我可以重新定义它为:

def write(self, data):
  sys.stderr.write(data)
  self.file.write(data+'\n')

我还学习了以下代码片段,它将记录所有的标准输出:

class Tee(object):
  def __init__(self):
    self.file = open('consolelog.dat', 'a')
    self.stdout = sys.stdout

  def __del__(self):
    sys.stdout = self.stdout
    self.file.close()

  def write(self, data):
    self.file.write(data)
    self.stdout.write(data)

sys.stdout = Tee()

我(失败的)尝试将所有这些内容汇总起来,是创建一个LoggedConsole对象,并将其传递给本地的Tee

console = LoggedConsole(locals={sys.stdout:LoggedExec()})
console.interact()

(我之前从未传递过本地变量,所以也许我在这里做得不正确,但我没有收到错误。)

无论如何,这将打开一个新的交互式控制台,并记录(关闭后)所有输入和错误,但不会输出。我已经苦思冥想了一段时间,感觉自己很接近,但也许还不够。

此外,在会话期间是否有一种方法可以实现所有这些操作?目前所有日志记录都在会话关闭后进行。

谢谢您的时间,对于这堵文字墙表示抱歉。

编辑: 出于可移植性考虑,我希望能够在标准Python解释器中完成此操作。

编辑2: Jaime的代码片段非常适合记录我需要的一切。不过,有没有办法让它实时执行,而不是等待会话结束?

编辑3: 找到了:)最终、可工作的代码片段:

import code
import sys

class Tee(object):
  def __init__(self, log_fname, mode='a'):
    self.log = open(log_fname, mode)

  def __del__(self):
    # Restore sin, so, se
    sys.stdout = sys.__stdout__
    sys.stdir = sys.__stdin__
    sys.stderr = sys.__stderr__
    self.log.close()

  def write(self, data):
    self.log.write(data)
    self.log.flush()
    sys.__stdout__.write(data)
    sys.__stdout__.flush()

  def readline(self):
    s = sys.__stdin__.readline()
    sys.__stdin__.flush()
    self.log.write(s)
    self.log.flush()
    return s

  def flush(foo):
    return

sys.stdout = sys.stderr = sys.stdin = Tee('consolelog.dat', 'w')

console = code.InteractiveConsole()
console.interact()

顺便说一句,不确定你的使用情况是什么,但是ipython notebook是交互式工作流程的一个很好的补充,如果你还没有看到的话。 - Kos
4个回答

8

我只在Python2.7中进行了测试,手头没有3的版本。

import code
import sys

class Tee(object):

  def __init__(self, log_fname, mode='a'):
    self.log = open(log_fname, mode)

  def __del__(self):
    # Restore sin, so, se
    sys.stdout = sys.__stdout__
    sys.stdir = sys.__stdin__
    sys.stderr = sys.__stderr__
    self.log.close()

  def write(self, data):
    self.log.write(data)
    sys.__stdout__.write(data)

  def readline(self):
    s = sys.__stdin__.readline()
    self.log.write(s)
    return s

# Tie the ins and outs to Tee.
sys.stdout = sys.stderr = sys.stdin = Tee('consolelog.dat', 'w')

console = code.InteractiveConsole()
console.interact()

这个非常好用。你有没有想过如何实现实时记录?即不等到会话关闭才写入?我在某个地方读到,调用sys.stdout.flush()等函数可以实现,但我不太确定如何实现。非常感谢你到目前为止所提供的帮助! - Zachary Allaun
1
搞定了。你需要对日志文件和标准输入/输出对象都调用flush()函数。感谢你把我引向正确的方向! - Zachary Allaun
很高兴能帮到你。你只需要刷新日志。没有必要刷新stdout/stderr。记住,日志是唯一实际写入文件的东西,因此刷新它就足够了。 - jaime
我也曾这样想,但是(出于某些我不理解的原因),在Py 3.2中,除非我刷新std对象,否则提示无法正确打印。我无法在2.7上复制此问题。我猜这就是使用3的代价 :)。 - Zachary Allaun

4

您可以使用Unix脚本命令,尝试以下操作:

script -a filename.txt
python
>> print("hi")
hi
>> exit()
exit

filename.txt会记录您在该会话中所做的一切,它可能看起来像这样:

Script started on Sat Dec 14 11:18:41 2013
python
>> print('hi')
hi
>> exit()
exit

Script done on Sat Dec 14 11:18:59 2013

3

请参阅Doug Hellmann的Virtualenv文章,了解如何记录iPython会话的日志:

如果您习惯于以这种交互方式工作,但希望在关闭会话后记录所做的操作以供将来参考,则可以使用IPython的日志记录功能将会话写入文件。要激活日志,请使用控制命令%logstart,如清单5所示。输出文件是一个Python源文件,因此很容易将其清理并在完成实验后转换为“真正”的模块。

In [6]: %logstart
Activating auto-logging. Current session state plus future input saved.
Filename       : ipython_log.py
Mode           : rotate
Output logging : False
Raw input log  : False
Timestamping   : False
State          : active

In [7]: a = 5

In [8]: b = 6

In [9]: c = a * b

In [10]: c

Out[10]: 30

In [11]: d = [ a, b, c]

In [12]: d

Out[12]: [5, 6, 30]

In [13]: %logstop

谢谢提供参考!不过我想让它在标准解释器中运行。我的目标是创建一个Python基础交互式教程(并在此过程中自学),我希望它能够便携,以便于可能使用它的朋友们受益。 - Zachary Allaun

0
你可以尝试使用我的日志工具。它还不完美,但已经解决了我的问题,这似乎与你的问题类似。

https://github.com/hholst80/loginteractive

它通过使用LD_PRELOAD将stdin.txt(或$ STDIN)导向stdout来实现。它适用于Python和Octave,尽管我还没有进行过太多测试。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接