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.
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.
py_call(+Call) is det
py_call(+Call, -Return) is det
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.
py_iter(+Iterator, -Value) is nondet
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:
- Evaluate Iterator as py_call/2 evaluates its first argument,
except the
Obj:Attr = Value
construct is not accepted.
- Call
__iter__
on the result to get the iterator itself.
- Get the
__next__
function of the iterator.
- 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.
- 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:
-
- 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.
py_setattr(+Target, +Name, +Value) is det- Set a Python attribute on an object. If Target is an atom, it is
interpreted as a module. Otherwise it is normally an object
reference. py_setattr/3 allows for chaining and behaves as if
defined as
py_setattr(Target, Name, Value) :-
py_call(Target, Obj, [py_object(true)]),
py_call(setattr(Obj, Name, Value)).
- Compatibility
- - PIP
- py_run(+String, +Globals, +Locals, -Result, +Options) is det[private]
- Interface to Py_CompileString() followed by PyEval_EvalCode().
Options:
- file_name(String)
- Errors are reported against this pseudo file name
- start(Token)
- One of
eval
, file
(default) or single
.
- Arguments:
-
Globals | - is a dict |
Locals | - is a dict |
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.
py_is_dict(@Term) is semidet- True if Term is a Prolog term that represents a Python dict.
- Compatibility
- - PIP. The SWI-Prolog version accepts both a SWI-Prolog dict
and the
{k:v,...}
representation. See py_dict_as
option of
py_call/2.
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.
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.
py_gil_owner(-Thread) is semidet- True when the Python GIL is owned by Thread. Note that, unless
Thread is the calling thread, this merely samples the current
state and may thus no longer be true when the predicate succeeds.
This predicate is intended to help diagnose deadlock problems.
Note that this predicate returns the Prolog threads that locked
the GIL. It is however possible that Python releases the GIL, for
example if it performs a blocking call. In this scenario, some
other thread or no thread may hold the gil.
py_func(+Module, +Function, -Return) is det
py_func(+Module, +Function, -Return, +Options) is det- Call Python Function in Module. The SWI-Prolog implementation is
equivalent to
py_call(Module:Function, Return)
. See py_call/2 for
details.
- Compatibility
- - PIP. See py_call/2 for notes. Note that, as this
implementation is based on py_call/2, Function can use chaining,
e.g.,
py_func(sys, path:append(dir), Return)
is accepted by this
implementation, but not portable.
py_dot(+ObjRef, +MethAttr, -Ret) is det
py_dot(+ObjRef, +MethAttr, -Ret, +Options) is det- Call a method or access an attribute on the object ObjRef. The
SWI-Prolog implementation is equivalent to
py_call(ObjRef:MethAttr,
Return)
. See py_call/2 for details.
- Compatibility
- - PIP. See py_func/3 for details.
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, ...}.
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, ...}.
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, ...}.
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, ...}.
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.
py_pp(+Term) is det
py_pp(+Term, +Options) is det
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
py_object_dir(+ObjRef, -List) is det
py_object_dict(+ObjRef, -Dict) is det- Examine attributes of an object. The predicate py_object_dir/2
fetches the names of all attributes, while py_object_dir/2 gets a
dict with all attributes and their values.
- Compatibility
- - PIP
py_obj_dir(+ObjRef, -List) is det
py_obj_dict(+ObjRef, -Dict) is det-
- deprecated
- - Use py_object_dir/2 or py_object_dict/2.
py_type(+ObjRef, -Type:atom) is det- True when Type is the name of the type of ObjRef. This is the same
as
type(ObjRef).__name__
in Python.
- Compatibility
- - PIP
py_isinstance(+ObjRef, +Type) is semidet- True if ObjRef is an instance of Type or an instance of one of the
sub types of Type. This is the same as
isinstance(ObjRef)
in
Python.
- Arguments:
-
Type | - is either a term Module:Type or a plain atom to refer to
a built-in type. |
- Compatibility
- - PIP
py_module_exists(+Module) is semidet- True if Module is a currently loaded Python module or it can be
loaded.
- Compatibility
- - PIP
py_hasattr(+ModuleOrObj, ?Name) is nondet- True when Name is an attribute of Module. The name is derived from
the Python built-in
hasattr()
. If Name is unbound, this enumerates
the members of py_object_dir/2.
- Arguments:
-
ModuleOrObj | - If this is an atom it refers to a module, otherwise
it must be a Python object reference. |
- Compatibility
- - PIP
py_import(+Spec, +Options) is det- Import a Python module. Janus imports modules automatically when
referred in py_call/2 and related predicates. Importing a module
implies the module is loaded using Python's
__import__()
built-in and added to a table that maps Prolog atoms to imported
modules. This predicate explicitly imports a module and allows it to
be associated with a different name. This is useful for loading
nested modules, i.e., a specific module from a Python package as
well as for avoiding conflicts. For example, with the Python
selenium
package installed, we can do in Python:
>>> from selenium import webdriver
>>> browser = webdriver.Chrome()
Without this predicate, we can do
?- py_call('selenium.webdriver':'Chrome'(), Chrome).
For a single call this is fine, but for making multiple calls it
gets cumbersome. With this predicate we can write this.
?- py_import('selenium.webdriver', []).
?- py_call(webdriver:'Chrome'(), Chrome).
By default, the imported module is associated to an atom created
from the last segment of the dotted name. Below we use an explicit
name.
?- py_import('selenium.webdriver', [as(browser)]).
?- py_call(browser:'Chrome'(), Chrome).
- Errors
- -
permission_error(import_as, py_module, As)
if there is
already a module associated with As.
py_module(+Module:atom, +Source:string) is det- Load Source into the Python module Module. This is intended to be
used together with the
string
quasi quotation that supports long
strings in SWI-Prolog. For example:
:- use_module(library(strings)).
:- py_module(hello,
{|string||
| def say_hello_to(s):
| print(f"hello {s}")
|}).
Calling this predicate multiple times with the same Module and
Source is a no-op. Called with a different source creates a new
Python module that replaces the old in the global namespace.
- Errors
- -
python_error(Type, Data)
is raised if Python raises an error.
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, passing Argv from the Prolog flag
py_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. |
py_connect_io is det[private]- If SWI-Prolog console streams are bound to something non-standard,
bind the Python console I/O to our streans.
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
py_add_lib_dir(+Dir) is det
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
. 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 using prolog_to_os_filename/2.
If Dir is a relative path, it is taken relative to Prolog source
file when used as a directive and relative to the process working
directory when called as a predicate.
- Compatibility
- - PIP. Note that SWI-Prolog uses POSIX file conventions
internally, mapping to OS conventions inside the predicates that
deal with files or explicitly using prolog_to_os_filename/2. Other
systems may use the native file conventions in Prolog.