SBCL程序I / O互操作性

时间:2019-01-26 21:15:10

标签: python subprocess common-lisp sbcl

我有一个要读取的程序,可以说它的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发出查询并读回响应。目前,我找不到任何适合我需要的项目或库,所以这就是为什么我尝试这样做。

2 个答案:

答案 0 :(得分:3)

[大多数答案都不是很有趣,因为提问者在发现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端的某个地方有 still 缓冲。您可以通过使用带有-u参数的Python以非常粗糙的方式摆脱这种情况。所以,最后

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

但是我认为真正的答案是去问Python人士这是怎么工作的,因为我不敢相信-u是正确的答案,或者很难做到。

答案 1 :(得分:2)

我设法使用下面的代码来完成此工作:

    (defun start-python ()
      (let ((process 
         (sb-ext:run-program "/usr/bin/python3" nil
                     :output :stream
                     :input :stream
                     :wait nil
                     :pty t
                     :error *standard-output*)))
        process))

    (defun read-until-newline (process)
      (let ((r ""))
        (loop for c = (read-char-no-hang (sb-ext:process-pty process))
           do (progn
            (if (or (not c) (char= c #\newline))
            (return-from read-until-newline r)
            (setf r (concatenate 'string r (format nil "~c" c))))))))

    (defun print-all-output (process &key (discard nil))
      (sleep 0.1)
      (loop 
         do (progn
          (if (listen (sb-ext:process-pty process))
              (if (not discard)
                  (print (read-until-newline process))
                  (read-until-newline process))
              (return)))))

    (defun send-to-python (process str)
      (format (sb-ext:process-pty process) str)
      (finish-output (sb-ext:process-pty process)))

    (defun test-process-stream ()
      (let* ((process (start-python)))
        (print-all-output process :discard t) ;;discard banner message
        (send-to-python process "X=[1,2,3,4,5]~%print(X[:2],X[2:])~%X~%")
        (print-all-output process)
        (sb-ext:process-close process)
        ))

非常感谢@ jkiiski,帮助我调试了这段代码。 诀窍是使用c_str()作为:pty的参数,然后使用流run-program与流程进行通信。之后,执行(sb-ext:process-pty process)将我们的输入刷新到程序中。然后至关重要的是稍等片刻,以便子进程可以累积输出。 (finish-output (sb-ext:process-pty process))之后,(sleep 0.1)将能够判断是否有输出等待。然后,它与(listen ...)循环读取字符,直到没有剩余字符为止。我用换行符分隔了输出,如(read-char-no-hang)所示 上面的代码产生以下输出:

(read-until-newline)

随后对">>> [1, 2] [3, 4, 5]^M" ">>> [1, 2, 3, 4, 5]^M" ">>> " 的任何调用都会递增地打印程序的输出。