PublicShow sourcejanus.pl -- Call Python from Prolog

This library implements calling Python from Prolog. It is available directly from Prolog if the janus package is bundled. The library provides access to an embedded Python instance. If SWI-Prolog is embedded into Python using the Python package janus-swi, this library is provided either from Prolog or from the Python package.

Normally, the Prolog user can simply start calling Python using py_call/2 or friends. In special cases it may be needed to initialize Python with options using py_initialize/3 and optionally the Python search path may be extended using py_add_lib_dir/1.

Source py_version is det
Print version info on the embedded Python installation based on Python sys.version. If a Python virtual environment (venv) is active, indicate this with the location of this environment found.
Source py_call(+Call) is det
Source py_call(+Call, -Return) is det
Source py_call(+Call, -Return, +Options) is det
Call Python and return the result of the called function. Call has the shape `[Target][:Action]*`, where Target is either a Python module name or a Python object reference. Each Action is either an atom to get the denoted attribute from current Target or it is a compound term where the first argument is the function or method name and the arguments provide the parameters to the Python function. On success, the returned Python object is translated to Prolog. Action without a Target denotes a buit-in function.

Arguments to Python functions use the Python conventions. Both positional and keyword arguments are supported. Keyword arguments are written as Name = Value and must appear after the positional arguments.

Below are some examples.

% call a built-in
?- py_call(print("Hello World!\n")).
true.

% call a built-in (alternative)
?- py_call(builtins:print("Hello World!\n")).
true.

% call function in a module
?- py_call(sys:getsizeof([1,2,3]), Size).
Size = 80.

% call function on an attribute of a module
?- py_call(sys:path:append("/home/bob/janus")).
true

% get attribute from a module
?- py_call(sys:path, Path)
Path = ["dir1", "dir2", ...]

Given a class in a file dog.py such as the following example from the Python documentation

class Dog:
    tricks = []

    def __init__(self, name):
        self.name = name

    def add_trick(self, trick):
        self.tricks.append(trick)

We can interact with this class as below. Note that $Doc in the SWI-Prolog toplevel refers to the last toplevel binding for the variable Dog.

?- py_call(dog:'Dog'("Fido"), Dog).
Dog = <py_Dog>(0x7f095c9d02e0).

?- py_call($Dog:add_trick("roll_over")).
Dog = <py_Dog>(0x7f095c9d02e0).

?- py_call($Dog:tricks, Tricks).
Dog = <py_Dog>(0x7f095c9d02e0),
Tricks = ["roll_over"]

If the principal term of the first argument is not Target:Func, The argument is evaluated as the initial target, i.e., it must be an object reference or a module. For example:

?- py_call(dog:'Dog'("Fido"), Dog),
   py_call(Dog, X).
   Dog = X, X = <py_Dog>(0x7fa8cbd12050).
?- py_call(sys, S).
   S = <py_module>(0x7fa8cd582390).

Options processed:

py_object(Boolean)
If true (default false), translate the return as a Python object reference. Some objects are always translated to Prolog, regardless of this flag. These are the Python constants None, True and False as well as instances of the Python base classes int, float, str or tuple. Instances of sub classes of these base classes are controlled by this option.
py_string_as(+Type)
If Type is atom (default), translate a Python String into a Prolog atom. If Type is string, translate into a Prolog string. Strings are more efficient if they are short lived.
py_dict_as(+Type)
One of dict (default) to map a Python dict to a SWI-Prolog dict if all keys can be represented. If {} or not all keys can be represented, Return is unified to a term {k:v, ...} or py({}) if the Python dict is empty.
Compatibility
- PIP. The options py_string_as and py_dict_as are SWI-Prolog specific, where SWI-Prolog Janus represents Python strings as atoms as required by the PIP and it represents Python dicts by default as SWI-Prolog dicts. The predicates values/3, keys/2, etc. provide portable access to the data in the dict.
Source py_iter(+Iterator, -Value) is nondet
Source py_iter(+Iterator, -Value, +Options) is nondet
True when Value is returned by the Python Iterator. Python iterators may be used to implement non-deterministic foreign predicates. The implementation uses these steps:
  1. Evaluate Iterator as py_call/2 evaluates its first argument, except the Obj:Attr = Value construct is not accepted.
  2. Call __iter__ on the result to get the iterator itself.
  3. Get the __next__ function of the iterator.
  4. Loop over the return values of the next function. If the Python return value unifies with Value, succeed with a choicepoint. Abort on Python or unification exceptions.
  5. Re-satisfaction continues at (4).

The example below uses the built-in iterator range():

?- py_iter(range(1,3), X).
X = 1 ;
X = 2.

Note that the implementation performs a look ahead, i.e., after successful unification it calls `__next__()` again. On failure the Prolog predicate succeeds deterministically. On success, the next candidate is stored.

Note that a Python generator is a Python iterator. Therefore, given the Python generator expression below, we can use py_iter(squares(1,5),X) to generate the squares on backtracking.

def squares(start, stop):
     for i in range(start, stop):
         yield i * i
Arguments:
Options- is processed as with py_call/3.
Compatibility
- PIP. The same remarks as for py_call/2 apply.
bug
- Iterator may not depend on janus.query(), i.e., it is not possible to iterate over a Python iterator that under the hoods relies on a Prolog non-deterministic predicate.
Source py_is_object(@Term) is semidet
True when Term is a Python object reference. Fails silently if Term is any other Prolog term.
Errors
- existence_error(py_object, Term) is raised of Term is a Python object, but it has been freed using py_free/1.
Compatibility
- PIP. The SWI-Prolog implementation is safe in the sense that an arbitrary term cannot be confused with a Python object and a reliable error is generated if the references has been freed. Portable applications can not rely on this.
Source py_free(+Obj) is det
Immediately free (decrement the reference count) for the Python object Obj. Further reference to Obj using e.g., py_call/2 or py_free/1 raises an existence_error. Note that by decrementing the reference count, we make the reference invalid from Prolog. This may not actually delete the object because the object may have references inside Python.

Prolog references to Python objects are subject to atom garbage collection and thus normally do not need to be freed explicitly.

Compatibility
- PIP. The SWI-Prolog implementation is safe and normally reclaiming Python object can be left to the garbage collector. Portable applications may not assume garbage collection of Python objects and must ensure to call py_free/1 exactly once on any Python object reference. Not calling py_free/1 leaks the Python object. Calling it twice may lead to undefined behavior.
Source py_with_gil(:Goal) is semidet
Run Goal as once(Goal) while holding the Phyton GIL (Global Interpreter Lock). Note that all predicates that interact with Python lock the GIL. This predicate is only required if we wish to make multiple calls to Python while keeping the GIL. The GIL is a recursive lock and thus calling py_call/1,2 while holding the GIL does not deadlock.
Source values(+Dict, +Path, ?Val) is semidet
Get the value associated with Dict at Path. Path is either a single key or a list of keys.
Compatibility
- PIP. Note that this predicate handle a SWI-Prolog dict, a {k:v, ...} term as well as py({k:v, ...}.
Source keys(+Dict, ?Keys) is det
True when Keys is a list of keys that appear in Dict.
Compatibility
- PIP. Note that this predicate handle a SWI-Prolog dict, a {k:v, ...} term as well as py({k:v, ...}.
Source key(+Dict, ?Key) is nondet
True when Key is a key in Dict. Backtracking enumerates all known keys.
Compatibility
- PIP. Note that this predicate handle a SWI-Prolog dict, a {k:v, ...} term as well as py({k:v, ...}.
Source items(+Dict, ?Items) is det
True when Items is a list of Key:Value that appear in Dict.
Compatibility
- PIP. Note that this predicate handle a SWI-Prolog dict, a {k:v, ...} term as well as py({k:v, ...}.
Source py_shell
Start an interactive Python REPL loop using the embedded Python interpreter. The interpreter first imports janus as below.
from janus import *

So, we can do

?- py_shell.
...
>>> query_once("writeln(X)", {"X":"Hello world"})
Hello world
{'truth': True}

If possible, we enable command line editing using the GNU readline library.

When used in an environment where Prolog does not use the file handles 0,1,2 for the standard streams, e.g., in swipl-win, Python's I/O is rebound to use Prolog's I/O. This includes Prolog's command line editor, resulting in a mixed history of Prolog and Pythin commands.

Source py_pp(+Term) is det
Source py_pp(+Term, +Options) is det
Source py_pp(+Stream, +Term, +Options) is det
Pretty prints the Prolog translation of a Python data structure in Python syntax. This exploits pformat() from the Python module pprint to do the actual formatting. Options is translated into keyword arguments passed to pprint.pformat(). In addition, the option nl(Bool) is processed. When true (default), we use pprint.pp(), which makes the output followed by a newline. For example:
?- py_pp(py{a:1, l:[1,2,3], size:1000000},
         [underscore_numbers(true)]).
{'a': 1, 'l': [1, 2, 3], 'size': 1_000_000}
Compatibility
- PIP
Source py_obj_dir(+ObjRef, -List) is det
Source py_obj_dict(+ObjRef, -Dict) is det
Examine attributes of an object. The predicate py_obj_dir/2 fetches the names of all attributes, while py_obj_dict/2 gets a dict with all attributes and their values.
Compatibility
- PIP
Source py_initialize(+Program, +Argv, +Options) is det
Initialize and configure the embedded Python system. If this predicate is not called before any other call to Python such as py_call/2, it is called lazily, passing the Prolog executable as Program, the non-Prolog arguments as Argv and an empty Options list.

Calling this predicate while the Python is already initialized is a no-op. This predicate is thread-safe, where the first call initializes Python.

In addition to initializing the Python system, it

  • Adds the directory holding janus.py to the Python module search path.
  • If Prolog I/O is not connected to the file handles 0,1,2, it rebinds Python I/O to use the Prolog I/O.
Arguments:
Options- is currently ignored. It will be used to provide additional configuration options.
Source py_lib_dirs(-Dirs) is det
True when Dirs is a list of directories searched for Python modules. The elements of Dirs are in Prolog canonical notation.
Compatibility
- PIP
Source py_add_lib_dir(+Dir) is det
Source py_add_lib_dir(+Dir, +Where) is det
Add a directory to the Python module search path. In the second form, Where is one of first or last. py_add_lib_dir/1 adds the directory as first. The property sys:path is not modified if it already contains Dir.

Dir is in Prolog notation. The added directory is converted to an absolute path using the OS notation.

The form py_add_lib_dir/0 may only be used as a directive, adding the directory from which the current Prolog source is loaded at the head of the Python search path. If py_add_lib_dir/1 or py_add_lib_dir/2 are used in a directive and the given directory is not absolute, it is resolved against the directory holding the current Prolog source.

Compatibility
- PIP. PIP only describes py_add_lib_dir/1.

Re-exported predicates

The following predicates are exported from this file while their implementation is defined in imported modules or non-module files loaded by this module.

Source py_obj_dir(+ObjRef, -List) is det
Source py_obj_dict(+ObjRef, -Dict) is det
Examine attributes of an object. The predicate py_obj_dir/2 fetches the names of all attributes, while py_obj_dict/2 gets a dict with all attributes and their values.
Source pyfunc(+Module, +Function, -Return) is det
Source pyfunc(+Module, +Function, +Kwargs, -Return) is det
Source pyfunc(+Module, +Function, +Kwargs, +Prolog_Opts, -Return) is det
XSB compatible wrappers for py_call/2. Note that the wrapper supports more call patterns.
Arguments:
Prolog_Opts- is ignored. We may add that to provide compatible return data.
Source py_call(+Call) is det
Source py_call(+Call, -Return) is det
Source py_call(+Call, -Return, +Options) is det
Call Python and return the result of the called function. Call has the shape `[Target][:Action]*`, where Target is either a Python module name or a Python object reference. Each Action is either an atom to get the denoted attribute from current Target or it is a compound term where the first argument is the function or method name and the arguments provide the parameters to the Python function. On success, the returned Python object is translated to Prolog. Action without a Target denotes a buit-in function.

Arguments to Python functions use the Python conventions. Both positional and keyword arguments are supported. Keyword arguments are written as Name = Value and must appear after the positional arguments.

Below are some examples.

% call a built-in
?- py_call(print("Hello World!\n")).
true.
% call a built-in (alternative)
?- py_call(builtins:print("Hello World!\n")).
true.
% call function in a module
?- py_call(sys:getsizeof([1,2,3]), Size).
Size = 80.
% call function on an attribute of a module
?- py_call(sys:path:append("/home/bob/janus")).
true
% get attribute from a module
?- py_call(sys:path, Path)
Path = ["dir1", "dir2", ...]

Given a class in a file dog.py such as the following example from the Python documentation

class Dog:
    tricks = []

    def __init__(self, name):
        self.name = name

    def add_trick(self, trick):
        self.tricks.append(trick)

We can interact with this class as below. Note that $Doc in the SWI-Prolog toplevel refers to the last toplevel binding for the variable Dog.

?- py_call(dog:'Dog'("Fido"), Dog).
Dog = <py_Dog>(0x7f095c9d02e0).
?- py_call($Dog:add_trick("roll_over")).
Dog = <py_Dog>(0x7f095c9d02e0).
?- py_call($Dog:tricks, Tricks).
Dog = <py_Dog>(0x7f095c9d02e0),
Tricks = ["roll_over"]

py_call/1 can also be used to set an attribute on a module or object using the syntax py_call(Obj:Attr = Value). For example:

?- py_call(dog:'Dog'("Fido"), Dog),
   py_call(Dog:owner = "Bob"),
   py_call(Doc:owner, Owner).
Dog = <py_Dog>(0x7ffff7112170),
Owner = "Bob".

If the principal term of the first argument is not Target:Func, The argument is evaluated as the initial target, i.e., it must be an object reference or a module. For example:

?- py_call(dog:'Dog'("Fido"), Dog),
   py_call(Dog, X).
   Dog = X, X = <py_Dog>(0x7fa8cbd12050).
?- py_call(sys, S).
   S = <py_module>(0x7fa8cd582390).

Options processed:

py_string_as(+Type)
If Type is atom (default), translate a Python String into a Prolog atom. If Type is string, translate into a Prolog string. Strings are more efficient if they are short lived.
py_object(Boolean)
It true (default false), translate the return as a Python object reference unless it is None, a boolean or a number. If the number class is subclassed, we return the object rather than the number.
Source py_add_lib_dir(+Dir) is det
Source py_add_lib_dir(+Dir, +Where) is det
Add a directory to the Python module search path. In the second form, Where is one of first or last. py_add_lib_dir/1 adds the directory as last.

Dir is in Prolog notation. The added directory is converted to an absolute path using the OS notation.

Source free_python_object(+ObjRef) is det
XSB compatible wrapper for py_free/1. Note that ObjRef is subject to (atom) garbage collection. Explicitly freeing an object can be desirable if it is large.
Source pyfunc(+Module, +Function, -Return) is det
Source pyfunc(+Module, +Function, +Kwargs, -Return) is det
Source pyfunc(+Module, +Function, +Kwargs, +Prolog_Opts, -Return) is det
XSB compatible wrappers for py_call/2. Note that the wrapper supports more call patterns.
Arguments:
Prolog_Opts- is ignored. We may add that to provide compatible return data.
Source py_call(+Call) is det
Source py_call(+Call, -Return) is det
Source py_call(+Call, -Return, +Options) is det
Call Python and return the result of the called function. Call has the shape `[Target][:Action]*`, where Target is either a Python module name or a Python object reference. Each Action is either an atom to get the denoted attribute from current Target or it is a compound term where the first argument is the function or method name and the arguments provide the parameters to the Python function. On success, the returned Python object is translated to Prolog. Action without a Target denotes a buit-in function.

Arguments to Python functions use the Python conventions. Both positional and keyword arguments are supported. Keyword arguments are written as Name = Value and must appear after the positional arguments.

Below are some examples.

% call a built-in
?- py_call(print("Hello World!\n")).
true.
% call a built-in (alternative)
?- py_call(builtins:print("Hello World!\n")).
true.
% call function in a module
?- py_call(sys:getsizeof([1,2,3]), Size).
Size = 80.
% call function on an attribute of a module
?- py_call(sys:path:append("/home/bob/janus")).
true
% get attribute from a module
?- py_call(sys:path, Path)
Path = ["dir1", "dir2", ...]

Given a class in a file dog.py such as the following example from the Python documentation

class Dog:
    tricks = []

    def __init__(self, name):
        self.name = name

    def add_trick(self, trick):
        self.tricks.append(trick)

We can interact with this class as below. Note that $Doc in the SWI-Prolog toplevel refers to the last toplevel binding for the variable Dog.

?- py_call(dog:'Dog'("Fido"), Dog).
Dog = <py_Dog>(0x7f095c9d02e0).
?- py_call($Dog:add_trick("roll_over")).
Dog = <py_Dog>(0x7f095c9d02e0).
?- py_call($Dog:tricks, Tricks).
Dog = <py_Dog>(0x7f095c9d02e0),
Tricks = ["roll_over"]

py_call/1 can also be used to set an attribute on a module or object using the syntax py_call(Obj:Attr = Value). For example:

?- py_call(dog:'Dog'("Fido"), Dog),
   py_call(Dog:owner = "Bob"),
   py_call(Doc:owner, Owner).
Dog = <py_Dog>(0x7ffff7112170),
Owner = "Bob".

If the principal term of the first argument is not Target:Func, The argument is evaluated as the initial target, i.e., it must be an object reference or a module. For example:

?- py_call(dog:'Dog'("Fido"), Dog),
   py_call(Dog, X).
   Dog = X, X = <py_Dog>(0x7fa8cbd12050).
?- py_call(sys, S).
   S = <py_module>(0x7fa8cd582390).

Options processed:

py_string_as(+Type)
If Type is atom (default), translate a Python String into a Prolog atom. If Type is string, translate into a Prolog string. Strings are more efficient if they are short lived.
py_object(Boolean)
It true (default false), translate the return as a Python object reference unless it is None, a boolean or a number. If the number class is subclassed, we return the object rather than the number.
Source py_pp(+Term) is det
Source py_pp(+Term, +Options) is det
Source py_pp(+Stream, +Term, +Options) is det
Pretty prints the Prolog translation of a Python data structure in Python syntax. This exploits pformat() from the Python module pprint to do the actual formatting. Options is translated into keyword arguments passed to pprint.pformat(). For example:
?- py_pp(py{a:1, l:[1,2,3], size:1000000}, [underscore_numbers(true)]).
{'a': 1, 'l': [1, 2, 3], 'size': 1_000_000}
Source pydot(+Module, +ObjRef, +MethAttr, -Ret) is det
Source pydot(+Module, +ObjRef, +MethAttr, +Prolog_Opts, -Ret) is det
XSB compatible wrappers for py_call/2.
Arguments:
Module- is ignored (why do we need that if we have ObjRef?)
Prolog_Opts- is ignored. See pyfunc/5.
Source py_pp(+Term) is det
Source py_pp(+Term, +Options) is det
Source py_pp(+Stream, +Term, +Options) is det
Pretty prints the Prolog translation of a Python data structure in Python syntax. This exploits pformat() from the Python module pprint to do the actual formatting. Options is translated into keyword arguments passed to pprint.pformat(). For example:
?- py_pp(py{a:1, l:[1,2,3], size:1000000}, [underscore_numbers(true)]).
{'a': 1, 'l': [1, 2, 3], 'size': 1_000_000}
Source pydot(+Module, +ObjRef, +MethAttr, -Ret) is det
Source pydot(+Module, +ObjRef, +MethAttr, +Prolog_Opts, -Ret) is det
XSB compatible wrappers for py_call/2.
Arguments:
Module- is ignored (why do we need that if we have ObjRef?)
Prolog_Opts- is ignored. See pyfunc/5.
Source pyfunc(+Module, +Function, -Return) is det
Source pyfunc(+Module, +Function, +Kwargs, -Return) is det
Source pyfunc(+Module, +Function, +Kwargs, +Prolog_Opts, -Return) is det
XSB compatible wrappers for py_call/2. Note that the wrapper supports more call patterns.
Arguments:
Prolog_Opts- is ignored. We may add that to provide compatible return data.
Source py_iter(+Iterator, -Value) is nondet
Source py_iter(+Iterator, -Value, +Options) is nondet
True when Value is returned by the Python Iterator. Python iterators may be used to implement non-deterministic foreign predicates. The implementation uses these steps:
  1. Evaluate Iterator as py_call/2 evaluates its first argument, except the Obj:Attr = Value construct is not accepted.
  2. Call __iter__ on the result to get the iterator itself.
  3. Get the __next__ function of the iterator.
  4. Loop over the return values of the next function. If the Python return value unifies with Value, succeed with a choicepoint. Abort on Python or unification exceptions.
  5. Re-satisfaction continues at (4).

The example below uses the built-in iterator range():

?- py_iter(range(1,3), X).
X = 1 ;
X = 2.

Note that the implementation performs a look ahead, i.e., after successful unification it calls `__next__()` again. On failure the Prolog predicate succeeds deterministically. On success, the next candidate is stored.

Note that a Python generator is a Python _iterator. Therefore, given the Python generator expression below, we can use py_iter(squares(1,5),X) to generate the squares on backtracking.

def squares(start, stop):
     for i in range(start, stop):
         yield i * i
Arguments:
Options- is processed as with py_call/3.
bug
- Iterator may not depend on janus.Query()