[英]Elixir can be used with Python / Ruby using Erlport library but it is not well maintained, is there any good alternative? [on hold]
[英]Erlport/Python STDOUT capture to Elixir
我正在嘗試將 STDOUT 從 Python/Erlport 通過管道傳輸回 Elixir。 我有:python
調用工作正常,我只想將 Python 中的 STDOUT 內容發送回 Elixir 進行日志記錄,但我無法理解如何實現這一點。 我知道即使我使用的是 Python 2.7,這也是可能的。
我在:python
模塊周圍有一個 Genserver 包裝器,以便我的調用工作如下:
pid = Python.start()
Python.call(pid, :bridge, :register_handler, [self()])
Python.call 看起來像這樣:
def call(pid, module, function, args \\ []) do
:python.call(pid, module, function, args)
end
任何來自:bridge
(即bridge.py
)的東西都會丟失到 STDOUT 除非我明確返回某些東西(顯然是停止函數)。 我可以做什么來捕獲標准輸出?
我的想法是調用Python.call(pid, :builtins, :print, [self()])
但這會導致一堆錯誤,我真的不知道這是否是正確的方向。
我實際上想把它傳送到鳳凰頻道,但這是簡單的部分(我希望)。 有什么建議嗎? 謝謝。
我的想法是調用
Python.call(pid, :builtins, :print, [self()])
但這會導致一堆錯誤,我真的不知道這是否是正確的方向。
self()
不是輸出去的地方——而是self()
是print
的參數,即 python 將打印出來的內容。
我認為erlport
只能處理 MFA 調用(模塊、函數、參數),並且因為print
不是python 2.7
的函數,所以我認為您需要在print
周圍包裝一個函數,例如:
我的打印文件:
def print_this(str):
print str
我只是想將 Python 中的 STDOUT 內容發送回 Elixir 進行日志記錄,但我無法理解如何實現這一點。 我知道即使我使用的是 Python 2.7 也是可能的
erlport文檔說:
作為一個方便的特性,ErlPort 還支持將 Python 的 STDOUT 重定向到 Erlang...
這似乎是默認設置,因此您無需執行任何操作即可將 python 標准輸出重定向到 elixir 標准輸出。 那么問題就變成了:“如何將 elixir stdout 記錄到文件中?”
我可以將 elixir stdout
記錄到這樣的文件中:
朋友.ex:
defmodule Friends do
use Export.Python
def go do
#Get path to logfile:
priv_path = :code.priv_dir(:friends)
logfile_path = Path.join([priv_path, "log", "mylog.log"])
#Redirect stdout:
{:ok, io_pid} = File.open(logfile_path, [:append])
Process.group_leader(self(), io_pid)
#Send output to stdout:
IO.puts "Am I in the log file??!"
python_path = Path.expand("lib/python")
{:ok, py} = Python.start(
python: "python2.7",
python_path: python_path
)
Python.call(py, "myprint", "print_this", ["hello world!"])
Python.call(py, "myprint", "print_this", ["goodbye..."])
Python.stop(py)
end
end
這是我的目錄結構:
friends
/lib
/friends
/python
myprint.py
friends.ex
/priv
/log
mylog.log
在 iex 中:
~/elixir_programs/friends$ iex -S mix
Erlang/OTP 20 [erts-9.3] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:10] [hipe] [kernel-poll:false]
Compiling 1 file (.ex)
Interactive Elixir (1.8.2) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> Friends.go
iex(2)>
在日志文件中:
Am I in the log file??!
hello world!
goodbye...
[33m[36m:ok[0m[33m[0m
(我不知道最后一行上的垃圾是什么。編輯:嗯......它是原子:ok
被其他一些東西包圍。)
如果我在 python_path 行上方注釋掉go()
所有內容,那么我會得到:
iex(1)> Friends.go
hello world!
goodbye...
:ok
在 erlang/elixir 文件 I/O 是通過啟動一個進程來處理的,該進程將請求發送到寫入文件或讀取文件。 我認為 stdout 會被發送到group_leader
任何進程,如果處理文件 I/O 的進程是 group_leader,那么 stdout 會被發送到文件。
我不知道當您使用GenServer
時,弄亂group_leader
是否會搞砸。 erlang 文檔中有一個警告:
在具有監督樹的應用程序中,組長應該很少更改,因為 OTP 假定其進程的組長是它們的應用程序主人。
.
對於其他被這種事情卡住的人:因為我在:python
實例周圍有一個 GenServer,我只是利用了handle_info
:
def handle_info({:python, message}, session) do
message |> String.split("\n", trim: true)
SomeWeb.Endpoint.broadcast("log", "update", %{body: message})
{:stop, :normal, session}
end
細節
為了按照@7stud 的建議更全面地概述我的解決方案,我將包括基於erlport
的更廣泛的方法和這篇很棒的文章。 因此,我有一個看起來像這樣的Python
模塊:
defmodule App.Python do
@doc """
Python instance pointing to priv/python.
"""
def start() do
path = [
:code.priv_dir(:prefect),
"python"
]|> Path.join()
{:ok, pid} = :python.start([
{:python_path, to_charlist(path)}
])
pid
end
def call(pid, module, function, args \\ []) do
:python.call(pid, module, function, args)
end
def cast(pid, message) do
:python.cast(pid, message)
end
def stop(pid) do
:python.stop(pid)
end
end
它是從處理其生成和終止的 GenServer 調用的:
defmodule App.PythonServer do
@doc """
Receives async. messages from Python instance.
"""
use GenServer
alias App.Python
def start_link() do
GenServer.start_link(__MODULE__, [])
end
def init(_args) do
pid = Python.start()
Python.call(pid, :bridge, :register_handler, [self()])
App.Application.broadcast_change
{:ok, pid}
end
def cast_draw(id) do
{:ok, pid} = start_link()
GenServer.cast(pid, {:id, id})
end
def call_draw(id) do
{:ok, pid} = start_link()
GenServer.call(pid, {:id, id}, 10_000)
end
def handle_call({:id, id}, _from, session) do
result = Python.call(session, :bridge, :draw, [id])
{:reply, result, session}
end
def handle_cast({:id, id}, session) do
Python.cast(session, id)
{:noreply, session}
end
def handle_info({:python, message}, session) do
msg = message |> format_response
{:ok, time} = Timex.now |> Timex.format("{h12}:{m}{am} {D}/{M}/{YYYY}")
AppWeb.Endpoint.broadcast("log", "update", %{time: time, body: msg, process: message})
{:stop, :normal, session}
end
def terminate(_reason, session) do
Python.stop(session)
App.Application.broadcast_change
:ok
end
defp format_response(message) do
if String.contains? message, "[result] Sent" do
message |> String.split("\n", trim: true) |> Enum.at(-2)
else
message |> String.split("\n", trim: true) |> Enum.take(-12) |> Enum.join("\n")
end
end
end
您可以在最后看到如果 STDOUT 沒有從bridge.py
(或任何其他 Python 模塊)返回某個字符串, bridge.py
返回一個bridge.py
。 說起來, bridge.py
看起來像這樣:
import os
import sys
import subprocess
from erlport.erlang import set_message_handler, cast
from erlport.erlterms import Atom
message_handler = None # reference to the elixir process to send
cmd = "xvfb-run -a python"
py = os.path.join("/home/ubuntu/app/priv/python/export.py")
def cast_message(pid, message):
cast(pid, message)
def register_handler(pid):
global message_handler
message_handler = pid
def handle_message(id):
try:
result = draw(id)
print result
if message_handler:
cast_message(message_handler, (Atom('python'), result))
except Exception, error:
print error
if message_handler:
cast_message(message_handler, (Atom('python'), error))
pass
def draw(id):
proc = subprocess.check_output(
"{0} {1} {2}".format(cmd, py, id), stderr = subprocess.STDOUT, shell = True
)
return proc
set_message_handler(handle_message)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.