Page
Library
Module
Module type
Parameter
Class
Class type
Source
This library exposes all OCaml modules as Python modules, generating bindings on the fly.
OCaml
>= 4.13Python
>= 3.7 (and >= 3.10 for pattern-matching support)The package can be installed via opam
:
opam install ocaml-in-python
installs the latest release,opam pin add -k path . && opam install ocaml-in-python
executed in a clone of this repository installs the latest development version.Once installed via opam
, the package should be registered in the Python environment. There are two options:
pip
using the following command:pip install --editable "`opam var ocaml-in-python:lib`"
export PYTHONPATH="`opam var share`/python/:$PYTHONPATH"
A very simple mean to test that the bindings are working properly is to invoke the OCaml standard library from Python.
import ocaml
print(ocaml.List.map((lambda x : x + 1), [1, 2, 3]))
# => output: [2;3;4]
In the following example, we invoke the ref
function from the OCaml standard library to create a value of type int ref
(a mutable reference to an integer), and the following commands show that the reference can be mutated from Python (a reference is a record with a mutable field contents
) and from OCaml (here by invoking the OCaml function incr
).
>>> x = ocaml.ref(1, type=int)
>>> x
{'contents':1}
>>> x.contents = 2
>>> x
{'contents':2}
>>> ocaml.incr(x)
>>> x
{'contents':3}
In the following example, we compile a module on the fly from Python.
import ocaml
m = ocaml.compile(r'''
let hello x = Printf.printf "Hello, %s!\n%!" x
type 'a tree = Node of { label : 'a; children : 'a tree list }
let rec height (Node { label = _; children }) =
1 + List.fold_left (fun accu tree -> max accu (height tree)) 0 children
let rec of_list nodes =
match nodes with
| [] -> invalid_arg "of_list"
| [last] -> Node { label = last; children = [] }
| hd :: tl -> Node { label = hd; children = [of_list tl] }
''')
m.hello("world")
# => output: Hello, world!
print(m.height(
m.Node(label=1, children=[m.Node(label=2, children=[])])))
# => output: 2
print(m.of_list(["a", "b", "c"]))
# => output: Node {label="a";children=[Node {label="b";children=[Node {label="c";children=[]}]}]}
try:
print(m.of_list([]))
except ocaml.Invalid_argument as e:
print(e)
# => output: Stdlib.Invalid_argument("of_list")
It is worth noticing that there is no need for type annotations: bindings are generated with respect to the interface obtained by type inference.
findlib
In the following example, we call the OCaml library parmap
from Python.
import ocaml
ocaml.require("parmap")
from ocaml import Parmap
print(Parmap.parmap(
(lambda x : x + 1), Parmap.A([1, 2, 3]), ncores=2))
# => output: [2, 3, 4]
The function ocaml.require
uses ocamlfind
to load parmap
. Bindings are generated as soon as ocaml.Parmap
is accessed (in the example, at line from ocaml import Parmap
). Parmap.A
is one of the two constructors of the type Parmap.sequence
.
The generation of bindings is driven by the types exposed by the compiled module interfaces (*.cmi
files): relying on the *.cmi
files allows the bindings to cover most of the OCaml definitions (there are some limitations though, see below) and to use the inferred types for modules whose interface is not explicitly specified by a .mli
file.
The following conversions are defined for built-in types:
int
, nativeint
int32
, int64
are mapped to Python int
;import ocaml
ocaml.print_endline(ocaml.string_of_int(42))
# => output: 42
print(ocaml.int_of_string("5") + 1)
# => output: 6
string
is mapped to Python str
import ocaml
ocaml.print_endline("Hello, World!")
# => output: Hello, World!
print(ocaml.String.make(3, "a") + "b")
# => output: aaab
char
is mapped to Python str
with a single characterimport ocaml
print(ocaml.int_of_char("a"))
# => output: 97
print(ocaml.char_of_int(65))
# => output: A
bool
is mapped to Python bool
(beware of different case convention: OCaml values false
and true
are mapped to Python values False
and True
respectively)import ocaml
print(ocaml.Sys.interactive.contents)
# => output: False
print(ocaml.string_of_bool(True))
# => output: true
float
is mapped to Python float
, and functions taking floats as arguments can take benefit from the Python automatic coercion from int
to float
import ocaml
print(ocaml.float_of_int(1))
# => output: 1.0
print(ocaml.cos(0))
# => output: 1.0
array
is mapped to a dedicated class ocaml.array
, which supports indexing, enumeration, pattern-matching (with Python >= 3.10) and in-place modification. When an OCaml array is converted to a Python object, the elements are converted on demand. There is an implicit coercion to array from all Python iterable types such as Python lists (but in-place modification is lost).import ocaml
arr = ocaml.Array.make(3, 0)
arr[1] = 1
print(ocaml.Array.fold_left((lambda x,y : x + y), 0, arr))
# => output: 1
ocaml.Array.sort(ocaml.compare, arr)
print(list(arr))
# => output: [0, 0, 1]
print(ocaml.Array.map((lambda x: x + 1), range(0, 4)))
# => output: [|1;2;3;4|]
# With Python 3.10:
match arr:
case [0, 0, 1]:
print("Here")
# => output: Here
It is worth noticing that Array.make
is a polymorphic function parameterized in the type of the elements of the constructed array, and by default the type parameter for polymorphic function with ocaml-in-python
is Py.Object.t
, the type of all Python objects. As such, the cells of the array arr
defined above can contain any Python objects, not only integers.
arr[0] = "Test"
print(arr)
# => output: [|"Test";0;1|]
We can create an array with a specific types for cells by expliciting the type parameter of Array.make
, by using the keyword parameter type
.
arr = ocaml.Array.make(3, 0, type=int)
arr[0] = "Test"
# TypeError: 'str' object cannot be interpreted as an integer
list
is mapped to a dedicated class ocaml.list
, which supports indexing, enumeration and pattern-matching (with Python >= 3.10). When an OCaml list is converted to a Python object, the elements are converted on demand. There is an implicit coercion to list from all Python iterable types such as Python lists.bytes
is mapped to a dedicated class ocaml.bytes
, which behaves as a mutable collection of characters.option
is mapped to a dedicated class ocaml.option
, only for values of the form Some x
where the type of x
allows the value None
. If the type of x
does not contain a value None
, the OCaml value Some x
is mapped directly to the conversion of x
. Conversely, the value Some x
can be constructed with ocaml.Some(x)
. The OCaml value None
is mapped to the Python value None
.print(ocaml.List.find_opt((lambda x : x > 1), [0,1], type=int))
# => output: None
print(ocaml.List.find_opt((lambda x : x > 1), [0,1,2], type=int))
# => output: 2
print(ocaml.List.find_opt((lambda x : x > 1), [0,1,2]))
# => output: Some(2)
In the last call to find_opt
, the default type parameter is Py.Object.t
which contains the value None
.
exn
is mapped to a dedicated class ocaml.exn
, which is a sub-class of Python Error
class, and exceptions are converted as other extension constructors: each exception is a sub-class of ocaml.exn
, and values can be indexed (if the exception constructor takes parameters), accessed by field name (for inline records) and supports pattern-matching (with Python >= 3.10). There is an implicit coercion from other sub-classes of Python Error
class to Py.E
, the OCaml exception defined in pyml
for Python exceptions. If an exception is raised between OCaml and Python code, the exception is converted and raised from one side to the other.try:
ocaml.failwith("Test")
except ocaml.Failure as e:
print(e[0])
# => output: Test
in_channel
and out_channel
are mapped to FileIO
objects. In the following example, the OCaml function open_out
is used to create a new file test
, and the string Hello
is written in this file through the Python method write
. Then, the file test
is opened for reading with the Python built-in open
, and the channel is read with the OCaml function really_input_string
.with ocaml.open_out("test") as f:
f.write(b"Hello")
with open("test", "r") as f:
print(ocaml.really_input_string(f, 5))
# => ouput: Hello
floatarray
is mapped to a dedicated class ocamlarray
, which derives from numpy
array (numpy
is required for the support of floatarray
).'t_1 -> ... -> 't_n -> 'r
are mapped to Python callable objects with n
arguments. Labelled arguments are mapped to mandatory keyword arguments, and optional arguments are mapped to optional keyword arguments. For polymorphic functions, the type parameters are assumed to be Py.Object.t
, except if there is a keyword argument type
: the associated value can either be a single type if there is a single type parameter, or a tuple of types giving the type parameters in the order of their apparition in the function signature, or a dictionary whose keys are the names of the type parameters (e.g., "a"
for 'a
).'t_1 * ... * 't_n
are mapped to OCaml tuples with n
components.Each OCaml type definition introduces a new Python class, except for type aliases, that are exposed as other names for the same class.
Records are accessible by field name or index (in the order of the field declarations), and the values of the fields are converted on demand. Mutable fields can be set in Python. In particular, the ref
type defined in the OCaml standard library is mapped to the Python class ocaml.ref
with a mutable field content
. Records support pattern-matching (with Python >= 3.10). There is an implicit coercion from Python dictionaries with matching field names.
For variants, there is a sub-class by constructor, which behaves either as a tuple or as a record. The values of the arguments are converted on demand. Variants support pattern-matching (with Python >= 3.10).
Sub-modules are mapped to classes, which are constructed on demand. For instance, the module Array.Floatarray
is exposed as ocaml.Array.Floatarray
, and, in particular, the function Array.Floatarray.create
is available as ocaml.Array.Floatarray.create
.
The following traits of the OCaml type system are not supported (yet):