Skip to main content
 首页 » 编程设计

python之SBCL 程序 I/O 互操作性

2025年05月04日20jiqing9006

我有一个我想阅读的程序,可以说是 python。所以我有这两个功能:

 
 
(defun start-python () 
  (let ((process  
     (sb-ext:run-program "/usr/bin/python" nil 
                 :output :stream 
                 :input :stream 
                 :wait nil 
                 :search t 
                 :error *standard-output*))) 
    process)) 
 
(defun test-process-stream () 
  (let ((process (start-python))) 
    (format (sb-ext:process-input process) "print 'hello world!'~%") 
    (finish-output (sb-ext:process-input process)) 
    ;;I do not want to call "close" because I will be still reading from the input 
    (close (sb-ext:process-input process)) 
    (print (read-line (sb-ext:process-output process))) 
    (when (listen (sb-ext:process-output process)) 
      (print (read-line (sb-ext:process-output process)))) 
 
    (close (sb-ext:process-output process)) 
    (sb-ext:process-close process) 
    )) 
 

我希望能够从 python 进程的输出中增量读取,同时向其提供输入。 我尝试了几种方法,甚至是这里提到的方法:SBCL: Gather output of run-program process while running

但我在 SBCL 中无法做到这一点。 在示例代码中,我调用了 close,因为这是我获得任何输出的唯一方式。否则它只会挂起。

我非常感谢任何指点,因为我坚持这一点。我什至尝试过 (listen ...) 和 (finish-output ...) 但它仍然卡在 (read-line ...)。与 (listen ...) 的唯一区别是它返回 false 并且不打印任何内容。在尝试阅读之前,我什至尝试了 (sleep 2) 。还是什么都没有。

编辑:最终我的目标是运行 swipl,它是 SWI-Prolog。我在这里以 python 为例。我想实现 lisp 和 prolog 之间的互操作性,这样我就可以向 prolog 发出查询并读回响应。目前我找不到任何适合我需要的项目或库,所以这就是我尝试这样做的原因。

请您参考如下方法:

[这个答案的大部分内容并不有趣,因为发问者问的是 Python,结果发现他们指的是 Prolog,所以我浪费了我的时间来解决他们所说的问题,而不是他们实际遇到的问题。我把它留在这里以防它对其他人有用。]

我认为这不是 SBCL 的问题。给定以下代码:

(defun call-with-process (f program args &rest keys &key &allow-other-keys) 
  (let ((process (apply #'sb-ext:run-program program args keys))) 
    (unwind-protect 
        (funcall f process) 
      (sb-ext:process-close process)))) 
 
(defmacro with-process ((process program args &rest keys &key &allow-other-keys) 
                        &body forms) 
  `(call-with-process 
    (lambda (,process) 
      ,@forms) 
    ,program ,args ,@keys)) 
 
(defun test-echo-process (&rest strings-to-send) 
  (with-process (p "/bin/cat" '() 
                   :wait nil 
                   :input ':stream 
                   :output ':stream) 
    (let ((i (sb-ext:process-input p)) 
          (o (sb-ext:process-output p))) 
      (dolist (s strings-to-send) 
        (format i "~A~%" s) 
        (finish-output i) 
        (format t "Sent ~A, got ~A~%" s (read-line o)))))) 

然后 test-echo-process 工作正常。

但是这个函数(等同于你的)挂起:

(defun test-python-process () 
  (with-process (p "/usr/bin/python" '() 
                   :wait nil 
                   :input ':stream 
                   :output ':stream) 
    (let ((i (sb-ext:process-input p)) 
          (o (sb-ext:process-output p))) 
      (format i "print 'here'~%") 
      (finish-output i) 
      (format t "~A~%" (read-line o))))) 

所以,实际上问题在于 Python 的行为方式。事实上,你可以证明这一点。这是终端的一些输出:

$ cat | python 
print "hi" 
print "there" 
hi 
there 
$ 

没有显示的是,在我键入第二个 print 命令后,我发送了一个 EOF(即 Unix 上的 ^D)。

因此,我认为 Python 非常合理地缓冲其输入,可能仅在它不是终端的情况下。

所以你需要做点什么来阻止这种情况的发生。作为最初的事情,我会把你想让 Python 运行的程序放在一个文件中,这样标准输入就只做一件事。但随后你会发现自己身处痛苦的世界。

如果你实现这个功能

(defun test-python-script (args &rest strings-to-send) 
  (with-process (p "/usr/bin/python" args 
                   :wait nil 
                   :input ':stream 
                   :output ':stream) 
    (let ((i (sb-ext:process-input p)) 
          (o (sb-ext:process-output p))) 
      (dolist (s strings-to-send) 
        (format i "~A~%" s) 
        (finish-output i) 
        (format t "sent ~A, got ~A~%" s (read-line o)))))) 

然后你可能会认为 echo.py 中的一些 Python 是这样的:

from sys import stdin, exit 
 
if __name__ == '__main__': 
    for l in stdin: 
        print "got " + l.strip() 
    exit(0) 

& 然后运行 ​​(test-python-script '("echo.py") "foo""bar") 就可以了。但是你至少在两个方面是错误的(你可以通过在命令行上运行 python echo.py 并看到它仍然在缓冲来检查这一点。

你错的第一种方式是在 Python 中使用文件作为迭代器内置了缓冲,你似乎无法避免。你可以通过编写一个无缓冲的迭代器来处理这个问题,所以 echo.py 现在是

from sys import stdin, exit 
 
class UnbufferedLineIterator(object): 
    # I take back what I said about 'perfectly reasonably' 
    def __init__(self, stream): 
        self.stream = stream 
 
    def __iter__(self): 
        return self 
 
    def next(self): 
        line = self.stream.readline() 
        if len(line) > 0: 
            return line 
        else: 
            raise StopIteration 
 
if __name__ == '__main__': 
    for l in UnbufferedLineIterator(stdin): 
        print "got " + l.strip() 
    exit(0) 

这可能行得通,但行不通,因为 仍然 在 Python 端的某处进行缓冲。您可以通过使用 -u 参数运行 Python 以非常粗略的方式摆脱它。所以,最后

* (test-python-script '("-u" "echo.py") "foo" "bar") 
sent foo, got got foo 
sent bar, got got bar 

但是我认为真正的答案是去问问 Python 的人这是如何工作的,因为我不敢相信 -u 是正确的答案或者它会这么难.