Easy Python from LFE/Erlang
Posted on December 27, 2014 by oubiwann

Recently there has been a great deal of interest in running Python from Erlang/LFE. In particular, these two posts have gotten a lot of attention. This post introduces a new library for LFE and Erlang that builds upon the work demonstrated in those posts, and furthermore, points towards a future of distributed Python ... a future that is just a few days away.
The library is py. It has an extensive README which is, in essence, a tutorial. As such this post won't cover all the material provided there – we'll take a look at some samples and show some new features that haven't been added to the README yet :-)
Getting the Code
Be sure to check out the requirements here! Then grab the code and get everything ready:
$ git clone git@github.com:lfex/py.git
$ cd py
$ make
$ . ./python/.venv/bin/activate
$ make repl-no-deps
This will put you into the LFE REPL with everything running and ready to execute some Python.
Using ``py``
Let's start off simple by taking a look at all the versions you have:
> (py-util:get-versions)
(#(erlang "17")
#(emulator "6.2")
#(driver-version "3.1")
#(lfe "0.9.0")
#(erlport "0.9.8")
#(py "0.0.1")
#(python
("3.4.2 (v3.4.2:ab2c023a9432, Oct 5 2014, 20:42:22)"
"[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)]")))
Now a Python call:
> (py:func 'datetime.datetime 'now)
#("datetime" #(2014 12 27 17 58 14 810870 undefined))
Module-level constants:
> (py:const 'math 'pi)
3.141592653589793
Instantiating objects:
> (py:init 'collections 'UserDict)
#("UserDict" ())
> (py:int #b("101010") '(#(base 2)))
42
> (py:init 'datetime 'date '(1923 4 2))
#("date" #(1923 4 1))
Calling methods:
> (set now (py:func 'datetime.datetime 'now))
#("datetime" #(2014 12 23 23 14 37 677463 undefined))
> (py:method now 'strftime '(#b("%Y.%m.d %H:%M:%S")))
"2014.12.d 23:14:37"
Object attributes:
> (py:attr now 'year)
2014
> (py:attr now 'microsecond)
677463
Operations on objects:
> (set later (py:func 'datetime.datetime 'now))
#("datetime" #(2014 12 23 23 21 25 714474 undefined))
> (set earlier now)
#("datetime" #(2014 12 23 23 14 37 677463 undefined))
> (set diff (py:sub later earlier))
#("timedelta" 0 408 37011)
> (py:attr diff 'seconds)
408
Python builtins:
> (py:dict)
#("dict" ())
> (py:dict '(#("a" 1) #("b" 2)))
#("dict" (#("b" 2) #("a" 1)))
> (py:any '(true true false false false true))
true
> (py:all '(true true false false false true))
false
> (py:round 0.666666667 5)
0.66667
> (py:range 7 42)
#($erlport.opaque python
#B(128 2 99 95 95 98 117 105 108 ...))
> (py:len (py:range 7 42))
35
> (py:pylist (py:range 7 42))
(7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
31 32 33 34 35 36 ...)
Python operators:
> (py:add 37 5)
42
> (py:mul 7 6)
42
> (py:sub -108 -150)
42
> (py:truediv 462 11)
42.0
> (py:floordiv 462 11)
42
> (py:gt 7 6)
true
> (py:le 7 6)
false
> (py:eq 42 42)
true
> (py:and- 60 13)
12
> (py:or- 60 13)
61
> (py:xor 60 13)
49
> (py:inv 60)
-61
> (py:rshift 60 2)
15
> (py:lshift 60 2)
240
Erlang?
Yes, all of this is easily available to Erlang too :-) That's the beauty of LFE – 100% Erlang Core compatible :-)
There is a link in the project's README file that points to example Erlang usage – be sure to check that out!
The ``py-sup`` LFE Supervisor
``py`` runs ErlPort Python servers using an Erlang supervision tree. Since ``py`` comes with an OTP application, the supervision tree gets started with the application (this happens automatically when you use the ``make repl`` and ``make repl-no-deps`` targets).
To get a list of running Python server Erlang process IDs:
> (py:get-python-pids)
(<0.36.0>)
The supervisor process ID:
> (py:get-sup-pid)
<0.35.0>
If your Python server process crashes, the supervisor will restart it. You can see this in action with the following:
> (erlang:exit (car (py:get-python-pids)) 'kill)
true
> (py:get-python-pids)
(<0.37.0>)
Notice the incremented process ID for the Python server.
Wait a few seconds and try it again:
> (erlang:exit (car (py:get-python-pids)) 'kill)
true
> (py:get-python-pids)
(<0.38.0>)
The default restart policy for ``py`` is to only restart if the Python server hasn't crashed more than 3 times in 1 second. This prevents run-away restarts in the cases of bad code, pathologically configured Python environment, etc.
Try killing the child Python server process rapidly several times in a row:
> (erlang:exit (car (py:get-python-pids)) 'kill)
true
> (erlang:exit (car (py:get-python-pids)) 'kill)
true
> (erlang:exit (car (py:get-python-pids)) 'kill)
true
> (erlang:exit (car (py:get-python-pids)) 'kill)
true
>
=INFO REPORT==== 27-Dec-2014::18:17:21 ===
application: py
exited: shutdown
type: temporary
Three was okay – but not four!
Support for a supervision tree is just the beginning, though ...
The Future
Current development is focused on three new features for the library:
- Running multiple Python servers at the same time
- Creating sophisticated schedulers that determine which Python node to execute on next
- Distributed Python: the crowning glory of Erlang/LFE
To contribute, you can check out the existing tickets here. You can also hop on the LFE mail list and ask questions, make suggestions, etc.