将Python与Elixir II混合

异步通讯

重要提示 :在继续之前,请阅读本文的第一部分

第一部分中,我们研究了如何使用Erlport从Elixir调用Python函数!

ErlPort是一个Elixir库,可以使用Erlang端口协议更轻松地将Erlang连接到许多其他编程语言。

但是,您可能已经注意到,调用过程必须等待Python完成处理并返回结果。 这适用于即时操作,无需花费很多时间。 否则,当您运行密集的Python函数时,它就不会很有趣。 因为您的Elixir程序将挂起,等待Python代码完成。

对于需要一段时间才能完成的操作,我们需要一种方法来调用Python函数,并在函数完成时在elixir代码中得到通知-类似于我们编写elixir程序时,进程与每个异步对话的过程。 这很有用,尤其是在您希望Elixir进程控制/监督多个Python操作的情况下。 我们可以同时运行Python函数,这会使生活变得更好。

在本文中,我们将探讨如何实现这一目标-Python和Elixir之间的异步通信!

我们可以推论的挑战,我们在药剂进程间通信的方式相同-通过使用cast -这使我们能够异步进程间通信。

以前,我们在从Python调用函数时主要使用erlport的 :python.call/4 。 这是同步的,并且elixir程序必须等待python函数完成并发送响应。

在这篇文章中,我们将使用:python.cast/2

了解:python.cast / 2

:python.cast/2GenServer.cast/2工作原理相同。 您给它提供进程ID(或注册的进程名称)以及要发送给该进程的消息。 与:python.call变体不同, :python.cast用于发送消息,而不是调用Python函数。 您的Elixir进程发送消息的方式相同。 Erlport为我们提供了一种接收发送到python实例的消息的机制。

Erlport Python模块

Erlport提供了一个python库 ,可帮助您处理elixir数据类型和过程。 您可以使用pip安装erlport。

pip install erlport #don't run this yet!

但是,Erlport Elixir存储库已随附Python库,该库会在运行时自动加载。 因此,如果您通过Elixir使用Erlport,则不必安装Erlport Python库。

下面是如何在Erlang / Elixir和Python之间映射数据

映射到Python数据类型的Erlang / Elixir数据类型( http://erlport.org/docs/python.html
映射到Erlang / Elixir数据类型的Python数据类型( http://erlport.org/docs/python.html

处理从Elixir发送到Python的消息

对于Elixir进程,您有一个接收块,在其中处理所有消息。 如果使用的是GenServer,则可以使用handle_callhandle_casthandle_info函数。 Erlport提供了一种类似的机制来处理发送到Python进程的消息。 set_message_handler是函数。 在erlport.erlang模块中。 它采用带有一个参数消息(即传入消息)的Python函数。 每当收到消息时, set_message_handler就会调用传递给set_message_handler的函数。 这就像一个回调函数。

从Python发送消息到Elixir

由于Elixir进程不知道是谁发送了强制转换消息,因此我们必须找到一种方法来知道python函数完成后将结果发送给哪个进程。 一种方法是注册要将结果发送到的Elixir进程的进程ID。

所述Erlport蟒模块提供cast功能用于发送消息给药剂处理。 使用此功能,我们可以在函数完成后将消息异步发送回Elixir进程!

放在一起

打开终端并使用mix创建一个新的Elixir项目

$ mix new elixir_python

添加依赖

erlport添加到依赖项mix.exs

defp deps do
[
{:erlport, "~> 0.9"} ,
]
end

然后安装项目依赖项。

$ cd elixir_python
$ mix deps.get

创建priv/python目录,您将在其中保留我们的python模块

$ mkdir -p priv/python

为Erlport相关功能创建一个elixir模块包装器。

#lib/python.ex
defmodule ElixirPython.Python do
    @doc """
Start python instance with custom modules dir priv/python
"""
def start() do
path = [
:code.priv_dir(:elixir_python),
"python"
]|> Path.join()

{:ok, pid} = :python.start([
{:python_path, to_charlist(path)}
])
pid
    end 
    def call(pid, m, f, a \\ []) do 
:python.call(pid, m, f, a)
end

def cast(pid, message) do
:python.cast(pid, message)
end

def stop(pid) do
:python.stop(pid)
end
 end 

为了使Elixir进程异步接收消息,我们将创建一个简单的GenServer模块

#lib/python_server.ex
defmodule ElixirPython.PythonServer do
use
GenServer
alias ElixirPython.Python
   def start_link() do 
GenServer.start_link(__MODULE__, [])
end
    def init(args) do
#start the python session and keep pid in state
python_session = Python.start()
#register this process as the message handler
Python.call(python_session, :test, :register_handler, [self()])
{:ok, python_session}
end
    def cast_count(count) do 
{:ok, pid} = start_link()
GenServer.cast(pid, {:count, count})
end

def
call_count(count) do
{:ok, pid} = start_link()
# :infinity timeout only for demo purposes
GenServer.call(pid, {:count, count}, :infinity)
end

def
handle_call({:count, count}, from, session) do
result = Python.call(session, :test, :long_counter, [count])
{:reply, result, session}
end
    def handle_cast({:count, count}, session) do 
Python.cast(session, count)
{:noreply, session}
end
    def handle_info({:python, message}, session) do         
IO.puts("Received message from python: #{inspect message}")

#stop elixir process
{:stop, :normal, session}
end
    def terminate(_reason, session) do 
Python.stop(session)
:ok
end
 end 

现在让我们创建python模块priv/python/test.py

#priv/python/test.py
import time
import sys
#import erlport modules and functions
from erlport.erlang import set_message_handler, cast
from erlport.erlterms import Atom

message_handler = None #reference to the elixir process to send result to

def cast_message(pid, message):
cast(pid, message)

def register_handler(pid):
#save message handler pid
global message_handler
message_handler = pid

def handle_message(count):
try :
print "Received message from Elixir"
print count
result = long_counter(count)
if message_handler:
#build a tuple to atom {:python, result}
cast_message(message_handler, (Atom('python'), result))
except Exception, e:
# you can send error to elixir process here too
# print e
pass
def long_counter(count=100):
#simluate a time consuming python function
i = 0
data = []
while i < count:
time.sleep(1) #sleep for 1 sec
data.append(i+1)
i = i + 1
return data

set_message_handler(handle_message) #set handle_message to receive all messages sent to this python instance

编译!

$ mix compile

跑!

$ iex -S mix
iex(1)> ElixirPython.PythonServer.call_count(4)
[1, 2, 3, 4] #printed after waiting 4sec
iex(2)> ElixirPython.PythonServer.call_count(10)
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10] #printed after waiting 10sec!

还要注意, call_count会导致iex挂起,直到我们从python获得返回结果为止

现在让我们异步调用相同的python函数!

iex(3)> ElixirPython.PythonServer.cast_count(4)
:ok
Received message from Elixir
4
Received message from python: [1, 2, 3, 4] # printed after 4sec, no wait
iex(4)> ElixirPython.PythonServer.cast_count(10)
:ok
Received message from Elixir
10
iex(5)>
nil
Received message from python: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] #printed after 10sec without blocking

请注意,我们的python函数完成后,我们如何继续在iex中工作并获得结果。 尝试使用大小不同的数字调用cast_count

这就对了! 现在,您可以在Elixir和Python之间异步通信。

编码愉快!

From: https://hackernoon.com/mixing-python-with-elixir-ii-async-e8586f9b2d53

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值