【问题标题】:Erlport/Python STDOUT capture to ElixirErlport/Python STDOUT 捕获到 Elixir
【发布时间】:2019-11-14 16:13:32
【问题描述】:

我正在尝试将 STDOUT 从 Python/Erlport 传输回 Elixir。我的:python 调用工作正常,我只想将 Python 中的 STDOUT 内容发送回 Elixir 进行日志记录,但我不知道如何实现这一点。我知道它是possible,即使我使用的是 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,除非我明确返回某些内容(显然是停止函数)。我该怎么做才能捕获 STDOUT?

我的想法是调用 Python.call(pid, :builtins, :print, [self()]) 之类的东西,但这会导致一堆错误,我真的不知道这是否是正确的方向。

我实际上想将它传送到 Phoenix 频道,但这是最简单的部分(我希望如此)。有什么建议吗?谢谢。

【问题讨论】:

    标签: python erlang elixir phoenix-framework gen-server


    【解决方案1】:

    我的想法是调用 Python.call(pid, :builtins, :print, [self()]) 之类的东西,但这会导致一堆错误,我真的 完全不知道这是否是正确的方向。

    self() 不是输出的位置——而是self()print 的参数,即python 将打印出什么。

    我认为erlport 只能处理MFA 调用(模块、函数、参数),并且因为print 不是python 2.7 中的函数,我认为您需要在print 周围包装一个函数,例如:

    myprint.py:

    def print_this(str):
        print str
    

    我只想将 Python 中的 STDOUT 内容发送回 Elixir 记录,但我无法理解如何实现这一目标。我知道 即使我使用的是 Python 2.7 也有可能

    erlport 文档说:

    作为一个方便的功能,ErlPort 还支持 Python 的重定向 标准输出到 Erlang...

    这似乎是默认设置,因此您无需执行任何操作即可将 python 标准输出重定向到 elixir 标准输出。那么问题就变成了:“如何将 elixir 标准输出记录到文件中?”

    我可以将 elixir stdout 记录到这样的文件中:

    friends.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 docs中有警告:

    在具有 监督树,因为 OTP 假定他们的组长 进程是它们的应用程序主控。

    .

    【讨论】:

    • 感谢您周到的回答。我会将其标记为已接受,因为它肯定比我的临时方法更好地解释了事情。
    • @cd-rum,啊,不要那样做。在您的回答中,您说,我在 :python 实例周围有一个 Genserver,--我认为如果您展示如何设置它会很有帮助。我最近使用 GenServer 来解决类似类型的问题。在我的例子中,一个称为函数的进程使该进程成为套接字的所有者,这意味着只有该进程才能从套接字发送和接收消息。我使用了一个GenServer的handle_call()来调用函数,这意味着GenServer成为了所有者,来自socket的消息由handle_info()处理。
    • 公平,我会尽快扩展我的答案。
    • @cd-rum,好的,我编辑了我的答案,这意味着您可以删除复选标记,当您扩展答案时,您可以接受自己的答案。
    • 我不知道最后一行是什么垃圾。编辑:嗯......它是原子:ok 被其他东西包围。) - “其他东西”由终端解释的颜色代码组成。 :ok 会在终端中显示为蓝色(或其他)。
    【解决方案2】:

    对于其他陷入这种事情的人:因为我在 :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 的建议更全面地概述我的解决方案,我将包括基于erlportthis great post 的更广泛的方法。因此,我有一个看起来像这样的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 看起来像这样:

    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)
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-06-01
      • 1970-01-01
      • 2015-08-17
      • 1970-01-01
      • 1970-01-01
      • 2013-02-05
      • 2017-09-03
      • 1970-01-01
      相关资源
      最近更新 更多