erlang_python — erlang_python v3.0.0
Search documentation of erlang_python
Default
Default<br>In-browser search
Settings
erlang_python
View Source
Combine Python's ML/AI ecosystem with Erlang's concurrency.<br>Run Python code from Erlang or Elixir with true parallelism, async/await support,<br>and seamless integration. Build AI-powered applications that scale.<br>Overview<br>erlang_python embeds Python into the BEAM VM, letting you call Python functions,<br>evaluate expressions, and stream from generators - all without blocking Erlang<br>schedulers.<br>Parallelism options:<br>Worker mode (default, recommended) - Works with any Python version. With free-threaded Python (3.13t+), provides true parallelism automatically.<br>OWN_GIL sub-interpreters (Python 3.14+) - Each interpreter has its own GIL, true parallelism.<br>BEAM processes - Fan out work across lightweight Erlang processes.<br>Key features:<br>Process-bound environments - Each Erlang process gets isolated Python state, enabling OTP-supervised Python actors<br>Async/await - Call Python async functions, gather results, stream from async generators<br>Dirty NIF execution - Python runs on dirty schedulers, never blocking the BEAM<br>Elixir support - Works seamlessly from Elixir via the :py module<br>Bidirectional calls - Python can call back into registered Erlang/Elixir functions<br>Message passing - Python can send messages directly to Erlang processes via erlang.send()<br>Type conversion - Automatic conversion between Erlang and Python types (including PIDs)<br>Streaming - Iterate over Python generators chunk-by-chunk<br>Virtual environments - Activate venvs for dependency isolation<br>AI/ML ready - Examples for embeddings, semantic search, RAG, and LLMs<br>Logging integration - Python logging forwarded to Erlang logger<br>Distributed tracing - Span-based tracing from Python code<br>Security sandbox - Blocks fork/exec operations that would corrupt the VM<br>Requirements<br>Erlang/OTP 27+<br>Python 3.12+ (3.13+ for free-threading)<br>C compiler (gcc, clang)<br>Building<br>rebar3 compile<br>Quick Start<br>Erlang<br>%% Start the application<br>application:ensure_all_started(erlang_python).
%% Call a Python function<br>{ok, 4.0} = py:call(math, sqrt, [16]).
%% With keyword arguments<br>{ok, Json} = py:call(json, dumps, [#{foo => bar}], #{indent => 2}).
%% Evaluate an expression<br>{ok, 45} = py:eval("sum(range(10))">>).
%% Evaluate with local variables<br>{ok, 25} = py:eval("x * y">>, #{x => 5, y => 5}).
%% Async calls with await<br>Ref = py:spawn_call(math, factorial, [100]),<br>{ok, Result} = py:await(Ref).
%% Fire-and-forget (no result)<br>ok = py:cast(erlang, send, [self(), {done, "task1">>}]).
%% Streaming from generators<br>{ok, [0,1,4,9,16]} = py:stream_eval("(x**2 for x in range(5))">>).Elixir<br># Start the application<br>{:ok, _} = Application.ensure_all_started(:erlang_python)
# Call Python functions<br>{:ok, 4.0} = :py.call(:math, :sqrt, [16])
# Evaluate expressions<br>{:ok, result} = :py.eval("2 + 2")
# With variables<br>{:ok, 100} = :py.eval("x * y", %{x: 10, y: 10})
# Call with keyword arguments<br>{:ok, json} = :py.call(:json, :dumps, [%{name: "Elixir"}], %{indent: 2})Erlang/Elixir Functions Callable from Python<br>Register Erlang or Elixir functions that Python code can call back into:<br>Erlang<br>%% Register a function<br>py:register_function(my_func, fun([X, Y]) -> X + Y end).
%% Call from Python - native import syntax (recommended)<br>{ok, Result} = py:exec("<br>from erlang import my_func<br>result = my_func(10, 20)<br>">>).<br>%% Result = 30
%% Or use attribute-style access<br>{ok, 30} = py:eval("erlang.my_func(10, 20)">>).
%% Legacy syntax still works<br>{ok, 30} = py:eval("erlang.call('my_func', 10, 20)">>).
%% Unregister when done<br>py:unregister_function(my_func).Elixir<br># Register an Elixir function<br>:py.register_function(:factorial, fn [n] -><br>Enum.reduce(1..n, 1, &*/2)<br>end)
# Call from Python - native import syntax<br>{:ok, 3628800} = :py.exec("""<br>from erlang import factorial<br>result = factorial(10)<br>""")
# Or use attribute-style access<br>{:ok, 3628800} = :py.eval("erlang.factorial(10)")Python Calling Syntax<br>From Python code, registered Erlang functions can be called in three ways:<br># 1. Import syntax (most Pythonic)<br>from erlang import my_func<br>result = my_func(10, 20)
# 2. Attribute syntax<br>import erlang<br>result = erlang.my_func(10, 20)
# 3. Explicit call (legacy)<br>import erlang<br>result = erlang.call('my_func', 10, 20)All three methods are equivalent. The import and attribute syntaxes provide<br>a more natural Python experience.<br>Reentrant Callbacks<br>Python→Erlang→Python callbacks are fully supported. When Python code calls<br>an Erlang function that in turn calls back into Python, the system handles<br>this transparently without deadlocking:<br>%% Register an Erlang function that calls Python<br>py:register_function(double_via_python, fun([X]) -><br>{ok, Result} = py:call('__main__', double, [X]),<br>Result<br>end).
%% Define Python functions<br>py:exec("<br>def double(x):<br>return x * 2
def process(x):<br>from erlang import call<br># This calls Erlang, which calls Python's double()<br>doubled = call('double_via_python', x)<br>return...