将命令管道输入Python REPL

时间:2014-08-28 20:41:36

标签: python read-eval-print-loop

我有一个包含Python语句的文件,我想以这样一种方式运行Python,即打印到stdout,如果这些命令在REPL中运行,将显示什么。

例如,如果文件是

1 + 4
'a' + 'b'

然后输出应为

>>> 1 + 4
5
>>> 'a' + 'b'
'ab'

有办法做到这一点吗?

5 个答案:

答案 0 :(得分:4)

使用code模块

(不是那样)快速且(大部分)脏了:

import sys
import code

infile = open('cmd.py')
def readcmd(prompt):
    line = infile.readline()
    if not line:
        sys.exit(0)

    print prompt,line.rstrip()
    return line.rstrip()

code.interact(readfunc=readcmd)

还有很大的改进空间,但现在已经很晚了。无论如何,例如:

sh$ cat cmd.py
1 + 4
'a' + 'b'

1/0

def f(x):
    return x*2

f(3)
sh$ python console.py 
Python 2.7.3 (default, Mar 13 2014, 11:03:55) 
[GCC 4.7.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>>  1 + 4
5
>>>  'a' + 'b'
'ab'
>>>  
>>>  1/0
Traceback (most recent call last):
  File "<console>", line 1, in <module>
ZeroDivisionError: integer division or modulo by zero
>>>  
>>>  def f(x):
...      return x*2
...  
>>>  f(3)
6

答案 1 :(得分:4)

您可以使用replwrap中的pexpect来实现此目标,即使采用python方法:

from pexpect import replwrap

with open("commands.txt", "r") as f:
    commands = [command.strip() for command in f.readlines()]

repl = replwrap.python()
for command in commands:
   print ">>>", command
   print repl.run_command(command),

返回:

python replgo.py 
>>> 1 + 4
5
>>> 'a' + 'b'
'ab'

您需要获取最新版本的pexpect。

答案 2 :(得分:3)

一些神奇的魔法可以帮到这里:

import ast
import itertools


def main():
    with open('test.txt', 'r') as sr:
        parsed = ast.parse(sr.read())
        sr.seek(0)
        globals_ = {}
        locals_ = {}
        prev_lineno = 0
        for node in ast.iter_child_nodes(parsed):
            source = '\n'.join(itertools.islice(sr, 0, node.lineno - prev_lineno))[:-1]
            print('>>> {}'.format(source))
            if isinstance(node, ast.Expr):
                print(eval(source, globals_, locals_))
            else:
                exec(source, globals_, locals_)
            prev_lineno = node.lineno

if __name__ == '__main__':
    main()

输入:

1 + 4
'a' + 'b'
a = 1
a

输出:

>>> 1 + 4
5
>>> 'a' + 'b'
ab
>>> a = 1
>>> a
1

这样做是通过使用ast模块解析源代码来查找每个单独语句的起始行和结束行号,然后根据它是否evalexec调用是一种陈述或表达。

上下文保存在globals_locals_

通过使用一些python沙箱来执行evalexec,您可以使这更安全。

答案 3 :(得分:1)

您可以将输入传输到pythons“code”模块。它将显示输出,但不会显示输入。

$ echo '1 + 1' | python -m code
Python 2.7.10 (v2.7.10:15c95b7d81dc, May 23 2015, 09:33:12) 
[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> 2

答案 4 :(得分:0)

我们可以通过欺骗 Python 命令启动交互式会话来实现此目的。这可以通过 unbuffer 来完成,它通常与 Linux 发行版中的 expect 工具一起提供。这种方法非常通用,适用于在交互调用时表现不同的各种程序。

以下命令将启动 REPL,即使(当前为空)输入来自管道:

$ printf '' | unbuffer -p python3
Python 3.8.8 (default, Feb 19 2021, 11:04:50)
[GCC 9.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.

一些烦人的行为使这项工作与交互式会话略有不同。首先,unbuffer一遇到EOF就会退出,所以我们需要一个小的延迟来确保Python有足够的时间启动:

$ (sleep 1; printf '') | unbuffer -p python3
Python 3.8.8 (default, Feb 19 2021, 11:04:50)
[GCC 9.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>

请注意,这次我们设法获得了 >>> 提示。

其次,我们的输入命令不会作为输出的一部分得到回显。例如:

$ (sleep 1; printf 'print("a" * 10)\n'; sleep 1) | unbuffer -p python3
Python 3.8.8 (default, Feb 19 2021, 11:04:50)
[GCC 9.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> aaaaaaaaaa
>>>

请注意,我们的输入 print("a" * 10)\n 不会出现在 >>> 提示之后,即使结果确实被打印出来。

我们可以使用 tee 将我们的命令复制到 stdout 和 REPL(我们使用进程替换执行)来解决这个问题:

$ (sleep 1; printf 'print("a" * 10)\n'; sleep 1) | tee >(unbuffer -p python3)
Python 3.8.8 (default, Feb 19 2021, 11:04:50)
[GCC 9.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> print("a" * 10)
aaaaaaaaaa
>>>

这似乎行为正常,但我们必须在每行之间设置延迟。这是一个自动执行此操作的脚本,从标准输入读取行:

#!/usr/bin/env bash
set -e

cat | (sleep 1; while IFS='' read -r LINE
do
 sleep 0.2
 echo "$LINE"
done; sleep 1) | tee >(unbuffer -p python3)

这似乎可以完成工作(我正在使用 printf 但这对于单独的文件同样有效;请注意,REPL 需要两个换行符来执行缩进块,就像在交互式会话中一样) :

$ printf 'if True:\n  print("hello")\nelse:\n  print("world")\n\n12345\n' | ./repl.sh
Python 3.8.8 (default, Feb 19 2021, 11:04:50)
[GCC 9.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> if True:
...   print("hello")
... else:
...   print("world")
...
hello
>>> 12345
12345
>>>

如果您想去除最终的 head 和启动噪音,例如

,我们可以通过标准文本处理工具(如 tail>>>
$ printf 'if True:\n  print("hello")\nelse:\n  print("world")\n\n12345\n' | ./repl.sh | tail -n+4 | head -n-1
>>> if True:
...   print("hello")
... else:
...   print("world")
...
hello
>>> 12345
12345