• Places
    • Home
    • Graphs
    • Prefixes
  • Admin
    • Users
    • Settings
    • Plugins
    • Statistics
  • Repository
    • Load local file
    • Load from HTTP
    • Load from library
    • Remove triples
    • Clear repository
  • Query
    • YASGUI SPARQL Editor
    • Simple Form
  • Help
    • Documentation
    • Tutorial
    • Roadmap
    • HTTP Services
  • Login

SWI-Prolog Python interface
All Application Manual Name SummaryHelp

  • Documentation
    • Reference manual
    • Packages
      • SWI-Prolog Python interface
        • Introduction
        • Data conversion
        • Janus by example
        • library(janus): Call Python from Prolog
        • Calling Prolog from Python
          • Janus class Query
          • Janus and Prolog truth
          • Janus class Term
          • Janus class PrologError
        • Janus and threads
        • 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 binding can also call Prolog from Python. This can both be used to realize call backs, i.e., the Python system embedded into Prolog calls Prolog, or after embedding SWI-Prolog into Python.

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 advice to import 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 consider the module to be named janus.

The Python module janus provides utility functions and defines the classes janus.Query(), janus.Term() and janus.PrologError(). We start our discussion by introducing the janus.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.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.once("Y is 1+1", {})       # Ok for "static" queries
{'Y': 2, 'truth': True}
>>> x = 1
>>> janus.once(f"Y is {x}+1", {})    # Do not use this
{'Y': 2, 'truth': True}             # See above

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.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 truth3Note 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 the string ’Undefined'. If evaluation of the query failed, all variable bindings are bound to the Python constant None and the truth key is False. The following Python function returns True if the Prolog system supports unbounded integers and False otherwise.

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

While janus.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.once(), the returned dict contains a truth field. This field cannot be False though and thus is either True or the string 'Undefined'.4The representation of Undefined is still under discussion.

import janus_swi as janus

def printRange(from, to):
    for d in janus.Query("between(F,T,X)", {"F":from, "T":to})
        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

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()

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.

dict janus.once(query, bindings={}, keep=False, truth=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.
>>> once("b_setval(a, 1)", keep=True)
{'truth': 'True'}
>>> 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 truth5As 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.2 for details.

>>> import janus_swi as janus
>>> janus.once("undefined")
{'truth': Undefined}
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")])
    
None janus.prolog()
Start the interactive Prolog toplevel. This is the Python equivalent of py_shell/0.

5.1 Janus class Query

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

Query janus.Query(query, bindings={}, keep=False)
As janus.once(), returning an iterator that provides an answer dict as janus.once() for each answer to query. Answers never have truth False. See discussion above.
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.
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.

5.2 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.2.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.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.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.once("russel:shaves(barber, X)", truth=janus.NO_TRUTHVALS)
{'truth': True, 'X': 'barber'}

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

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

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

5.3 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(...)
Instances are never created explicitly by the user. An instance is created by handling a term prolog(Term) tho the data conversion processes. 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.4 Janus class PrologError

Class janus.PrologError(), derived from the Python class Exception represents a Prolog exception that typically results from calling janus.once() or using janus.Query(). 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.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.

ClioPatria (version V3.1.1-48-gf653ce4)