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