This modules allows almost seamless interaction between Lua and Python, with
on-the-fly conversion (with good defaults) of most data types back and forth.
This allows, for example, to use numpy
, scipy
, or matplotlib
directly
with Torch tensors.
Dependencies:
- glog
- boost
- python2.7 (Manually edit the CMake for older versions, 3+ is not supported)
- thpp (If you don't have folly or thrift,
you can still build thpp with
THPP_NOFB=1 ./build.sh
, and fbpython should still work)
luarocks make rockspec/*
Throughout this document, we'll refer to this module as py
:
local py = require('fb.python')
At the core, there are two main functions in this module:
py.exec(code, locals)
executes the given Python code (given as a string). The string is a sequence of statements and does not return a value (Python distinguishes between statements and expressions). Iflocals
is specified, it must be a Lua table containing variables that the Python code will treat as local variables; if not specified, the code is executed at global (file) level (in the__main__
module).py.eval(code, locals)
evaluates the Python code and returns the result
Example:
py.exec([=[
import numpy as np
def foo(x):
return x + 1
]=])
print(py.eval('foo(a) + 10', {a = 42})) -- prints 53
There are a few things to note in the example above:
- Python is sensitive to indentation. When executing code, especially multiple
lines, use the multiline string syntax: strings start with
[=...=[
, where you can have an arbitrary number of equal signs between the open brackets, and end with a matching]=...=]
sequence with the same number of equal signs between the closed brackets. - Values are converted magically between Lua and Python. The eval call creates
a local Python environment with one variable (named
a
) that has the value 42 (and Pythonfloat
type); the return value (also a Pythonfloat
) is converted to a Lua number. The exact conversion rules are detailed in the next sections.
The Lua and Python data models do not match exactly, so conversions between Lua and Python types are lossy (and heuristic). They should work well in most cases, but you can override them by using opaque references (see below). Data is transferred between Lua and Python by value; that is, variables on the Python side are copies of the corresponding variables on the Lua side. Tables are copied deeply; tensors share data (so mutating tensor elements will be reflected back) but not metadata (so resizing won't be reflected back).
For example:
a = {foo = 'bar'}
py.exec([=[
a['foo'] = 'baz'
global b
b = a
]=], {a = a})
print(a['foo']) -- prints bar; Python change not reflected back
a['foo'] = 'meow'
py.exec([=[
print(b['foo'])
]=]) -- prints baz; Lua change not reflected back
- Lua numbers become Python
float
, unless they're used as indexes in a Lua table, in which case they become Pythonint
. (If you use non-integral values as keys in your table, you deserve the pain, as floating point approximations will be your downfall) - Lua strings become Python
str
objects, that is, strings of bytes, not characters. (In Python 3, these would bebytes
objects). That is, they are not assumed to be valid in any character set; you may convert them to Unicode on the Python side if you wish. - Lua
nil
becomes PythonNone
; this one is obvious. - Lua booleans become Python
bool
; this one is also obvious. - Lua tables become Python
list
ordict
, based on a heuristic: iftable[1]
exists buttable[0]
andtable[-1]
don't, we assume it's alist
, otherwise it's adict
. (The0
and-1
checks were added to handle Lua'sarg
built-in table, which, unlike the Lua convention, uses0
and negative indexes). Note that Python uses 0-based indexing, and Lua uses 1-based indexing; the first element of a Lua list-like table isx[1]
which becomesx[0]
on the Python side. Regardless of whether a Lua table becomes a Pythonlist
ordict
, numeric keys becomeint
, notfloat
. - Torch tensors become
numpy.ndarray
objects with share data (so modifying a tensor element from Python is reflected back to Lua) but not metadata (so resizing the tensor won't be)
Note that Lua nil
is different from Python None
; there are some situations
where you may not use nil
(you can't have nil
values in a table, for
example -- that's the same as having no value for that key). In one of these
cases, if you want to pass None
to Python, use py.None
:
py.exec('print(a)', {a = nil})
-- won't print None; will complain about undefined variable 'a'
py.exec('print(a)', {a = py.None})
-- prints None
Note that the only way to convert a Python object to Lua is to use
py.eval
; this is true regardless of whether you use the default conversions
or opaque references (see below).
- Python numeric types (
int
,long
,float
,numpy.float32
,numpy.float64
, and othernumpy
scalars) become Lua numbers - Python
str
objects (strings of bytes) become Lua strings - Python
unicode
objects (strings of characters) are encoded using UTF-8 and are then converted to Lua strings - Python
None
becomes Luanil
- Python
bool
objects become Lua booleans - Python
list
andtuple
objects become Lua tables; note that Python uses 0-based indexing, while Lua uses 1-based indexing, so the first element of a Pythonlist
ortuple
isx[0]
which becomesx[1]
on the Lua side. - Python
dict
objects become Lua tables numpy.ndarray
objects become Torch tensors which share data (so modifying a tensor element from Lua is reflected back to Python) but not metadata (so resizing the tensor won't be)
Sometimes, the above conversions are not sufficient (you might want to force a
Lua number to be a Python long
, or you might want to capture references to
Python objects of arbitrary types). In this case, we support opaque references,
which encapsulate any Python object. They can be used in place of Lua values
when passing arguments to Python:
local x = py.int(42) -- x wraps a Python int, not float
py.exec('print(type(x))', {x = x}) -- prints <type 'int'>
You may create opaque references using the following functions:
py.import(module)
imports a Python module and returns an opaque reference to itpy.int(x)
,py.long(x)
,py.float(x)
convert a Lua number to a Python number of the appropriate typepy.str(x)
converts a Lua string to a Pythonstr
py.unicode(x)
converts a Lua string to a Pythonunicode
object, assuming UTF-8 encodingpy.tuple(x)
,py.list(x)
,py.dict(x)
convert a Lua table to a Python container of the appropriate typepy.ref(x)
converts an arbitrary Lua object to a Python reference (using the Lua-to-Python conversion rules shown above)py.reval(code, locals)
is similar toeval
, but returns an opaque reference rather than converting the result back to Lua; note that this allows you to create references to Python objects that can't be converted to Lua (such as objects of class types, modules, etc)
Also, py.eval(ref)
converts an opaque reference to Lua, using the
Python-to-Lua conversion rules shown above.
Opaque references support function calls, arithmetic operations, attribute and
item lookup without going through py.exec
or py.eval
; these operations
are transparently bridged to Python and the results, if any, become
opaque references themselves (to allow chaining) but can be converted back to
Lua using py.eval
.
Function calls on opaque references support variable and keyword arguments
using the py.args
and py.kwargs
magic placeholders. If ref
is a
(callable) opaque reference, args
is a list-like table, and kwargs
is a
table, then
ref(arg1, arg2, arg3, py.args, args, py.kwargs, kwargs)
is the same as the Python call
ref(arg1, arg2, arg3, *args, **kwargs)
Example tying all this together:
-- np is opaque reference to Python numpy module
local np = py.import('numpy')
-- t1 is opaque reference to numpy.ndarray
local t1 = np.tri(10).transpose()
-- t2 is t1 converted to torch Tensor
local t2 = py.eval(t1)
local nltk = py.import('nltk')
local tokenized = py.eval(nltk.word_tokenize('Hello world, cats are awesome'))
(Again, note that operations on opaque references always return opaque
references, and you need py.eval
at the end of an operation chain to
convert back to Lua)
For convenience, there are functions leval
, lexec
, and lreval
which do
the same as the corresponding functions without the l
prefix, but they don't
take a locals
second argument; they execute the Python code in the context of
the current variables visible from Lua. (That is, they convert all local and
global Lua variables visible at the point of the call, ignoring errors). This
is quite slow, and therefore should only be used interactively.
a = 42
py.lexec('print(a)') -- prints 42.0