【问题标题】:Piping commands into the Python REPL将命令导入 Python REPL
【发布时间】:2014-08-28 20:41:36
【问题描述】:

我有一个包含 Python 语句的文件,我想以这样的方式运行 Python,它可以将这些命令在 REPL 中运行时显示的内容打印到标准输出。

例如,如果文件是

1 + 4
'a' + 'b'

那么输出应该是

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

有没有办法做到这一点?

【问题讨论】:

  • 视窗? nx ?跨平台?
  • Linux,虽然我真的希望有一个跨平台的方法。

标签: python read-eval-print-loop


【解决方案1】:

你可以使用pexpect中的replwrap来实现这个目标,甚至还有一个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。

【讨论】:

  • 这应该被更新、更简单和独立的答案 stackoverflow.com/a/67511928/1950432 取代它不需要第三方库,并且几乎可以在任何有 python 解释器和 unix shell 的地方工作。
【解决方案2】:

使用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

【讨论】:

    【解决方案3】:

    一些 ast 魔法可以在这里提供帮助:

    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 来提高安全性。

    【讨论】:

      【解决方案4】:

      您可以将您的输入通过管道传输到 python 的“代码”模块。它会显示输出,但不会显示输入。

      $ 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
      

      【讨论】:

        【解决方案5】:

        我们可以通过诱使 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.
        >>>
        

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

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

        $ (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 不会出现在 &gt;&gt;&gt; 提示符之后,即使结果会被打印出来。

        我们可以使用tee 来解决这个问题,将我们的命令复制到标准输出和 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
        >>>
        

        如果您想删除最终的 &gt;&gt;&gt; 和启动噪音,我们可以通过标准的文本处理工具(如 headtail)进行管道传输,例如

        $ 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
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2016-11-30
          • 1970-01-01
          • 2010-11-08
          • 2018-06-01
          • 1970-01-01
          • 1970-01-01
          • 2015-04-20
          • 1970-01-01
          相关资源
          最近更新 更多