SWI-Prolog Python interface
All Application Manual Name SummaryHelp

  • Documentation
    • Reference manual
    • Packages
      • SWI-Prolog Python interface
        • Introduction
        • Data conversion
        • Janus by example - Prolog calling Python
        • library(janus): Call Python from Prolog
        • Calling Prolog from Python
          • Janus iterator query
          • Janus iterator apply
          • Janus access to Python locals and globals
          • Janus and Prolog truth
          • Janus class Term
          • Janus class PrologError
        • Janus and threads
        • Janus and signals
        • Janus versions
        • Janus as a Python package
        • Prolog and Python
        • Janus performance evaluation
        • Python or C/C++ for accessing resources?
        • Janus platforms notes
        • Compatibility to the XSB Janus implementation
        • Status of Janus

5 Calling Prolog from Python

The Janus interface can also call Prolog from Python. Calling Prolog from Python is the basis when embedding Prolog into Python using the Python package janus_swi. However, calling Prolog from Python is also used to handle call backs. Mutually recursive calls between Python and Prolog are supported. They should be handled with some care as it is easy to crash the process due to a stack overflow.

Loading janus into Python is realized using the Python package janus-swi, which defines the module janus_swi. We do not call this simply janus to allow coexistence of Janus for multiple Prolog implementations. Unless you plan to interact with multiple Prolog systems in the same session, we advise importing janus for SWI-Prolog as below.

import janus_swi as janus

If Python is embedded into SWI-Prolog, the Python module may be imported both as janus and janus_swi. Using janus allows the same Python code to be used from different Prolog systems, while using janus_swi allows the same code to be used both for embedding Python into Prolog and Prolog into Python. In the remainder of this section we assume the Janus functions are available in the name space janus.

The Python module janus provides utility functions and defines the classes janus.query(), janus.apply(), janus.Term(), janus.Undefined() and janus.PrologError().

The Python calling Prolog interface consist of four primitives, distinguishing deterministic vs. non-deterministic Prolog queries and two different calling conventions which we name functional notation and relational notation. The relational calling convention specifies a Prolog query as a string with an input dict that provides (input) bindings for part of the variables in the query string. The results are represented as a dict that holds the bindings of the output variables and the truth value (see section 5.4). For example:

>>> janus.query_once("Y is sqrt(X)", {'X':2})
{'truth': True, 'Y': 1.4142135623730951}

The functional notation calling convention specifies the query as a module, predicate name and input arguments. It calls the predicate with one argument more than the number of input arguments and translates the binding of the output argument to Python. For example

>>> janus.apply_once("user", "plus", 1, 2)
3

The table below summarizes the four primitives.

Relational notationFunctional notation
detjanus.query_once() janus.apply_once()
nondetjanus.query() janus.apply()

We start our discussion by introducing the janus.query_once(query,inputs) function for calling Prolog goals as once/1. A Prolog goal is constructed from a string and a dict with input bindings and returns output bindings as a dict. For example

>>> import janus_swi as janus
>>> janus.query_once("Y is X+1", {"X":1})
{'Y': 2, 'truth': True}

Note that the input argument may also be passed literally. Below we give two examples. We strongly advise against using string interpolation for three reasons. Firstly, the query strings are compiled and cached on the Prolog sided and (thus) we assume a finite number of distinct query strings. Secondly, string interpolation is sensitive to injection attacks. Notably inserting quoted strings can easily be misused to create malicious queries. Thirdly and finally, serializing and deserializing the data is generally slower then using the input dictionary, especially if the data is large. Using a dict for input and output together with a (short) string to denote the goal is easy to use and fast.

>>> janus.query_once("Y is 1+1", {})    # Ok for "static" queries
{'Y': 2, 'truth': True}
>>> x = 1
>>> janus.query_once(f"Y is {x}+1", {}) # WRONG, See above
{'Y': 2, 'truth': True}

The output dict contains all named Prolog variables that (1) are not in the input dict and (2) do not start with an underscore. For example, to get the grandparents of a person given parent/2 relations we can use the code below, where the _GP and _P do not appear in the output dict. This both saves time and avoids the need to convert Prolog data structures that cannot be represented in Python such as variables or arbitrary compound terms.

>>> janus.query_once("findall(_GP, parent(Me, _P), parent(_P, _GP), GPs)",
               {'Me':'Jan'})["GPs"]
[ 'Kees', 'Jan' ]

In addition to the variable bindings the dict contains a key truth5Note that variable bindings always start with an uppercase latter. that represents the truth value of evaluating the query. In normal Prolog this is a Python Boolean. In systems that implement Well Founded Semantics, this may also be an instance of the class janus.Undefined(). See section 5.4 for details. If evaluation of the query failed, all variable bindings are bound to the Python constant None and the truth key has the value False. The following Python function returns True if the Prolog system supports unbounded integers and False otherwise.

def hasBigIntegers():
    janus.query_once("current_prolog_flag(bounded,false)")['truth']

While janus.query_once() deals with semi-deterministic goals, the class janus.query() implements a Python iterator that iterates over the solutions of a Prolog goal. The iterator may be aborted using the Python break statement. As with janus.query_once(), the returned dict contains a truth field. This field cannot be False though and thus is either True or an instance of the class janus.Undefined()

import janus_swi as janus

def printRange(fr, to):
    for d in janus.query("between(F,T,X)", {"F":fr, "T":to}):
        print(d["X"])

The call to janus.query() returns an object that implements both the iterator protocol and the context manager protocol. A context manager ensures that the query is cleaned up as soon as it goes out of scope - Python typically does this with for loops, but there is no guarantee of when cleanup happens, especially if there is an error. (You can think of a with statement as similar to Prolog's setup_call_cleanup/3.) Using a context manager, we can write

def printRange(fr, to):
    with janus.query("between(F,T,X)", {"F":fr, "T":to}) as d_q:
        for d in d_q:
            print(d["X"])

Iterators may be nested. For example, we can create a list of tuples like below.

def double_iter(w,h):
    tuples=[]
    for yd in janus.query("between(1,M,Y)", {"M":h}):
        for xd in janus.query("between(1,M,X)", {"M":w}):
            tuples.append((xd['X'],yd['Y']))
    return tuples

or, using context managers:

def doc_double_iter(w,h):
    tuples=[]
    with janus.query("between(1,M,Y)", {"M":h}) as yd_q:
        for yd in yd_q:
            with janus.query("between(1,M,X)", {"M":w}) as xd_q:
                for xd in xd_q:
                    tuples.append((xd['X'],yd['Y']))
    return tuples

After this, we may run

>>> demo.double_iter(2,3)
[(1, 1), (2, 1), (1, 2), (2, 2), (1, 3), (2, 3)]

In addition to the iterator protocol that class janus.query() implements, it also implements the methods janus.query.next() and janus.query.close(). This allows for e.g.

    q = query("between(1,3,X)")
    while ( s := q.next() ):
        print(s['X'])
    q.close()

or

  try:
      q = query("between(1,3,X)")
      while ( s := q.next() ):
          print(s['X'])
  finally:
      q.close()

The close() is called by the context manager, so the following is equivalent:

    with query("between(1,3,X)") as q:
        while ( s := q.next() ):
            print(s['X'])

But, iterators based on Prolog goals are fragile. This is because, while it is possible to open and run a new query while there is an open query, the inner query must be closed before we can ask for the next solution of the outer query. We illustrate this using the sequence below.

>>> q1 = query("between(1,3,X)")
>>> q2 = query("between(1,3,X)")
>>> q2.next()
{'truth': True, 'X': 1}
>>> q1.next()
Traceback (most recent call last):
...
swipl.Error: swipl.next_solution(): not inner query
>>> q2.close()
>>> q1.next()
{'truth': True, 'X': 1}
>>> q1.close()

Failure to close a query typically leaves SWI-Prolog in an inconsistent state and further interaction with Prolog is likely to crash the process. Future versions may improve on that. To avoid this, it is recommended that you use the query with a context manager, that is using the Python constwith statement.

dict janus.query_once(query, inputs={}, keep=False, truth_vals=TruthVals.PLAIN_TRUTHVALS)
Call query using bindings as once/1, returning a dict with the resulting bindings. If bindings is omitted, no variables are bound. The keep parameter determines whether or not Prolog discards all backtrackable changes. By default, such changes are discarded and as a result, changes to backtrackable global variables are lost. Using True, such changes are preserved.
>>> query_once("b_setval(a, 1)", keep=True)
{'truth': 'True'}
>>> query_once("b_getval(a, X)")
{'truth': 'True', 'X': 1}

If query fails, the variables of the query are bound to the Python constant None. The bindings object includes a key truth6As this name is not a valid Prolog variable name, this cannot be ambiguous. that has the value False (query failed, all bindings are None), True (query succeeded, variables are bound to the result converting Prolog data to Python) or an instance of the class janus.Undefined(). The information carried by this instance is determined by the truth parameter. Below is an example. See section 5.4 for details.

>>> import janus_swi as janus
>>> janus.query_once("undefined")
{'truth': Undefined}

See also janus.cmd() and janus.apply_once(), which provide a fast but more limited alternative for making ground queries (janus.cmd()) or queries with leading ground arguments followed by a single output variable.

Compatibility
PIP.
dict janus.once(query, inputs={}, keep=False, truth_vals=TruthVals.PLAIN_TRUTHVALS)
Deprecated. Renamed to janus.query_once().
Any janus.apply_once(module, predicate, *input, fail=obj)
Functional notation style calling of a deterministic Prolog predicate. This calls module:predicate(Input ... , Output), where Input are the Python input arguments converted to Prolog. On success, Output is converted to Python and returned. On failure a janus.PrologError() exception is raised unless the fail parameter is specified. In the latter case the function returns obj. This interface provides a comfortable and fast calling convention for calling a simple predicate with suitable calling conventions. The example below returns the home directory of the SWI-Prolog installation.
>>> import janus_swi as janus
>>> janus.apply_once("user", "current_prolog_flag", "home")
'/home/janw/src/swipl-devel/build.pdf/home'
Compatibility
PIP.
Truth janus.cmd(module, predicate, *input)
Similar to janus.apply_once(), but no argument for the return value is added. This function returns the truth value using the same conventions as the truth key in janus.query_once(). For example:
>>> import janus_swi as janus
>>> cmd("user", "true")
True
>>> cmd("user", "current_prolog_flag", "bounded", "true")
False
>>> cmd("user", "undefined")
Undefined
>>> cmd("user", "no_such_predicate")
Traceback (most recent call last):
  File "/usr/lib/python3.10/code.py", line 90, in runcode
    exec(code, self.locals)
  File "<console>", line 1, in <module>
janus.PrologError: '$c_call_prolog'/0: Unknown procedure: no_such_predicate/0

The function janus.query_once() is more flexible and provides all functionality of janus.cmd(). However, this function is faster and in some scenarios easier to use.

Compatibility
PIP.
None janus.consult(file, data=None, module='user’)
Load Prolog text into the Prolog database. By default, data is None and the text is read from file. If data is a string, it provides the Prolog text that is loaded and file is used as identifier for source locations and error messages. The module argument denotes the target module. That is where the clauses are added to if the Prolog text does not define a module or where the exported predicates of the module are imported into.

If data is not provided and file is not accessible this raises a Prolog exception. Errors that occur during the compilation are printed using print_message/2 and can currently not be captured easily. The script below prints the train connections as a list of Python tuples.

    import janus_swi as janus

    janus.consult("trains", """
    train('Amsterdam', 'Haarlem').
    train('Amsterdam', 'Schiphol').
    """)

    print([d['Tuple'] for d in
           janus.query("train(_From,_To),Tuple=_From-_To")])
    
Compatibility
PIP. The data and module keyword arguments are SWI-Prolog extensions.
None janus.prolog()
Start the interactive Prolog toplevel. This is the Python equivalent of py_shell/0.

5.1 Janus iterator query

Class janus.query() is similar to the janus.query_once() function, but it returns a Python iterator that allows for iterating over the answers to a non-deterministic Prolog predicate.

The iterator also implements the Python context manaager protocol (for the Python with statement).

query janus.query(query, inputs={}, keep=False)
As janus.query_once(), returning an iterator that provides an answer dict as janus.query_once() for each answer to query. Answers never have truth False. See discussion above.
Compatibility
PIP. The keep is a SWI-Prolog extension.
Query janus.Query(query, inputs={}, keep=False)
Deprecated. This class was renamed to janus.query(.)
dict|None janus.query.next()
Explicitly ask for the next solution of the iterator. Normally, using the query as an iterator is to be preferred. See discussion above. q.next() is equivalent to next(q) except it returns None if there are no more values instead of raising the StopIteration exception.
None janus.query.close()
Close the query. Closing a query is obligatory. When used as an iterator, the Python destructor (__del__()) takes care of closing the query. However, Python does not guarantee when the destructor will be called, so it is recommended that the context manager protocol is used (with the Python with statement), which closes the query when the query goes out of scope or when an error happens.
Compatibility
PIP.

5.2 Janus iterator apply

Class janus.apply() is similar to janus.apply_once(), calling a Prolog predicate using functional notation style. It returns a Python iterator that enumerates all answers.

apply janus.apply(module, predicate, *input)
As janus.apply_once(), returning an iterator that returns individual answers. The example below uses Python list comprehension to create a list of integers from the Prolog built-in between/3.
>>> list(janus.apply("user", "between", 1, 6))
[1, 2, 3, 4, 5, 6]
Compatibility
PIP.
any|None janus.apply.next()
Explicitly ask for the next solution of the iterator. Normally, using the apply as an iterator is to be preferred. See discussion above. Note that this calling convention cannot distinguish between the Prolog predicate returning @none and reaching the end of the iteration.
None janus.apply.close()
Close the query. Closing a query is obligatory. When used as an iterator, the Python destructor (__del__()) takes care of closing the query.
Compatibility
PIP.

5.3 Janus access to Python locals and globals

Python provides access to dictionaries holding the local variables of a function using locals() as well as the global variables stored as attributes to the module to which the function belongs as globals(). The Python C API provides PyEval_GetLocals() and PyEval_GetGlobals(), but these return the scope of the Janus API function rather than user code, i.e., the global variables of the janus module and the local variables of the running Janus interface function.

Python code that wishes Prolog to access its scope must pass the necessary scope elements (local and global variables) explicitly to the Prolog code. It is possible to pass the entire local and or global scope by the output of locals() and/or globals(). Note however that a dict passed to Prolog is translated to its Prolog representation. This representation may be prohibitively large and does not allow Prolog to modify variables in the scope. Note that Prolog can access the global scope of a module as attributes of this module, e.g.

increment :-
    py_call(demo:counter, V0),
    V is V0+1,
    py_setattr(demo, counter, V).

5.4 Janus and Prolog truth

In traditional Prolog, queries succeed or fail. Systems that implement tabling with Well Founded Semantics such as XSB and SWI-Prolog define a third truth value typically called undefined. Undefined results may have two reasons; (1) the program is logically inconsistent or (2) restraints have been applied in the derivation.

Because classical Prolog truth is dominant, we represent the success of a query using the Python booleans True and False. For undefined answers we define a class janus.Undefined() that may represent different levels of detail on why the result is undefined. The notion of generic undefined is represented by a unique instance of this class. The three truth values are accessible as properties of the janus module.

janus.true
This property has the Python boolean True
janus.false
This property has the Python boolean False
janus.undefined
This property holds a unique instance of class janus.Undefined()

5.4.1 Janus classed Undefined and TruthVal

The class janus.Undefined() represents an undefined result under the Well Founded Semantics.

Undefined janus.Undefined(term=None)
Instances are never created explicitly by the user. They are created by the calls to Prolog initiated from janus.query_once() and janus.query().

The class has a single property class term that represents either the delay list or the residual program. See janus.TruthVal() for details.

Enum janus.TruthVal()
This class is a Python enumeration. Its values are passed as the optional truth parameter to janus.query_once() and janus.query(). The defined instances are
NO_TRUTHVALS
Undefined results are reported as True. This is quite pointless in the current design and this may go.
PLAIN_TRUTHVALS
Return undefined results as janus.undefined, a unique instance of the class janus.Undefined().
DELAY_LISTS
Return undefined results as an instance of class janus.Undefined(). thats holds the delay list in Prolog native representation. See call_delays/2.
RESIDUAL_PROGRAM
Return undefined results as an instance of class janus.Undefined(). thats holds the residual program in Prolog native representation. See call_residual_program/2.

The instances of this enumeration are available as attributed of the janus module.

For example, given Russel's paradox defined in Prolog as below.

:- module(russel, [shaves/2]).

:- table shaves/2.

shaves(barber,P) :- person(P),  tnot(shaves(P,P)).
person(barber).
person(mayor).

From Python, we may ask who shaves the barber in four ways as illustrated below. Note that the Prolog representations for janus.DELAY_LISTS and janus.RESIDUAL_PROGRAM use the write_canonical/1 notation. They may later be changed to use a more human friendly notation.

# Using NO_TRUTHVALS
>>> janus.query_once("russel:shaves(barber, X)", truth_vals=janus.NO_TRUTHVALS)
{'truth': True, 'X': 'barber'}

# Using default PLAIN_TRUTHVALS (default)
>>> janus.query_once("russel:shaves(barber, X)")
{'truth': Undefined, 'X': 'barber'}

# Using default DELAY_LISTS
>>> janus.query_once("russel:shaves(barber, X)", truth_vals=janus.DELAY_LISTS)
{'truth': :(russel,shaves(barber,barber)), 'X': 'barber'}

# Using default RESIDUAL_PROGRAM
>>> janus.query_once("russel:shaves(barber, X)", truth_vals=janus.RESIDUAL_PROGRAM)
{'truth': [:-(:(russel,shaves(barber,barber)),tnot(:(russel,shaves(barber,barber))))], 'X': 'barber'}

5.5 Janus class Term

Class janus.Term() encapsulates a Prolog term. Similarly to the Python object reference (see py_is_object/1), the class allows Python to represent arbitrary Prolog data, typically with the intend to pass it back to Prolog.

Term janus.Term(*args)
Instances are never created explicitly by the user. An instance is created by handling a term prolog(Term) to the data conversion process. As a result, we can do
?- py_call(janus:echo(prolog(hello(world))), Obj,
           [py_object(true)]).
Obj = <py_Term>(0x7f7a14512050).
?- py_call(print($Obj)).
hello(world)
Obj = <py_Term>(0x7f7a14512050).
Term janus.Term.__str__()
Return the output of print/1 on the term. This is what is used by the Python function print().
Term janus.Term.__repr__()
Return the output of write_canonical/1 on the term.

5.6 Janus class PrologError

Class janus.PrologError(), derived from the Python class Exception represents a Prolog exception that typically results from calling janus.query_once(), janus.apply_once(), janus.query() or janus.apply(). The class either encapsulates a string on a Prolog exception term using janus.Term. Prolog exceptions are used to represent errors raised by Prolog. Strings are used to represent errors from invalid use of the interface. The default behavior gives the expected message:

>>> x = janus.query_once("X is 3.14/0")['X']
Traceback (most recent call last):
  ...
janus.PrologError: //2: Arithmetic: evaluation error: `zero_divisor'

At this moment we only define a single Python class for representing Prolog exceptions. This suffices for error reporting, but does not make it easy to distinguish different Prolog errors. Future versions may improve on that by either subclassing janus.PrologError or provide a method to classify the error more easily.

PrologError janus.PrologError(TermOrString)
The constructor may be used explicitly, but this should be very uncommon.
String janus.PrologError.__str__()
Return a human readable message for the error using message_to_string/2
String janus.PrologError.__repr__()
Return a formal representation of the error by means of write_canonical/1.