Picstus is a two-way interface between Python and Sicstus Prolog. It allows using Sicstus from Python and vice versa, where one language is seen as a module of another. Programs in the two languages can even mutually call one another to an arbitrary depth.
This document first describes the predicates for calling Python from Prolog. Then follows the richer interface for the opposite direction, calling Prolog from Python. The last part, which will probably be less interesting for most readers, describes the low-level structures which are seen from Python and are common to both interfaces.
| Contents |
Prolog to Python interface is implemented in a module which is surprisingly called python. To use it, import it with the usual consult(python).
The module contains a few basic predicates and a few utility predicates that are based on the basic ones. This section describes the former.
X to a Python variable a. This is one of the ways:
%v are given in the section on data conversion below.
X.
picstus.Term, see the documentation for the Python part) or when the function returns a list or a tuple. For instance, Python's function
l in the above example), we even don't need the quotes.
None), the result in a Prolog atom none. For example, to call a Python function sumtwo defined as
Module python also contains several predicates that are defined in Prolog. They implement a few common operations.
modulename. Implementation is somewhat nasty since Python's import statement is a statement, not function, and since the function __import__ returns a module instead of putting it into the namespace. So the implementation constructs a string import <modulename> and gives it to Python's function exec, which is executed through Prolog predicate exec...
from <modulename> import *.python:exec and python:evalIn predicates python:exec/1 and python:eval/2 the first argument can be constructed using the < operator, where the right-hand side is a list with the terms to replace the corresponding control characters in the string on the left-hand side.
For convenience, if there is only one control character, and the term you want to substitute it with is not a list, you can give a single term instead of a list.
Here's a list of all control characters.
print (2+3) * 4 and passed to Python.
Floats are rounded down.
string/1 predicate, described below, is not needed: all lists are treated as string. For convenience, %s also accepts integers and floats, which are then treated as if the control character was %i or %f. Hence, the followint example
%v is replaced by the variable's name. Hence, the above example is equivalent to the following code in Python (names of the temporary variables can be different):
== operator understands it) are put into the same variable. Here's an example:
X doesn't change to [1, 2, 3]. (A Prolog programmer would of course find changing the term unacceptable, while a Python programmer without much experience in Prolog could expect X to change...)
%v, except that the term is not converted to Python's native type, but left as an instance of picstus.Term. This way you can pass more complex structures to Python expressions and parse them in Python.The terms given as arguments to the python:call/3 predicate, the second argument of python:set predicate and also the terms that are put into temporary variables through %v control character described above, are converted into Python objects of corresponding types: Prolog integers and floats become Python integers and floats, atoms become strings and Prolog lists become Python lists, where each element is, recursively, converted. Atom none becomes a Python's None. Compound terms (with exception of lists and the two predicates described below) remain Prolog objects and the Python code can access such objects as described below, in the section on low level structures.
Here are a few predicates that can be used to control the conversion.
write expect a list and treat it as a string. But it is a problem for Picstus, since it cannot know when the list should be converted to a string and when not; to be on the safe side, it leaves a list as a list.
string.
term.
In the second case, the object still printed out as if it was an integer, but printing out its type reveals that it remained a Prolog term (picstus.Term). The equivalent effect would be achieved by using %t instead of %v and omitting the predicate term.
Storing such terms in Python is not a good idea. Read on to find why.
none is an atom which represents Python's constant None.For a more active use - throwing Prolog's and Python's data back and forth - you need to be aware that (as will be explained with more technical details later) Python and Prolog have drastically different memory managements. In Prolog, all terms created inside the query are thrown away when the query is done. Calling any of the above predicates is considered a query, hence a term stored in a Python variable can be dead immediately upon return. The following code is OK (and also tells something about how well unification works in picstus!)
a still holds a reference to it, not knowing what has happened outside (really, the Picstus interface code has no way of knowing whether python:set was called like in the former or in the latter example). When we request python:exec("print a"), it nicely informs us that the term is gone. Note that the trick through which picstus determines whether the term is still with us or not, may not be foolproof, so it may be possible to devise ways to get erroneous output or maybe even crash picstus by deliberately storing some deeply nested terms.
The interface to Python is useful for two purposes. Python is an excellent scripting language, so you can use it to implement the procedural parts which would be irksome in Prolog. Second, Python delivers a vast collection of modules for any purpose you may think of. In this section, we are gonna show a few.
How do you read a web page in Prolog through Picstus? Trivial.
One of the most irritating thing in typical Prolog implementations is a complete lack of any cryptographic library. ("Lying is a skill like any other, and if you want to maintain a level of excellence you have to practice constantly."; Garak, Star Trek DS 9). Let us show how to compute a MD5 digest of the page we got in the previous example.
The module that takes care of that is md5. We constructed an instance of class md5 and stored it to a Python variable m. We fed the web page (_X) to the m's method update(<string<) and called m.hexdigest() to get the digest. Method update returns no result which, in Python, means that it returns None. Not being interested in that, we assigned it to _.
For a more useful example, let us write a predicate which get all the links from the given web page. Here's the definition.
You have already seen the module urllib and how we call it. Module re is a very powerful module for regular expressions. The one we constructed first says it's case insensitive ((?i)), then follows a href, possibly some whitespace, followed by = and maybe some more white space and a quote. Part [^"]* represents any number of any characters but a quote; we closed this part of the string into parentheses to define it as a group. Finally, we have the closing quote.
We have passed the regular expression as an atom, not a string. This is simply to avoid the need for escaping the backslashes and quotes.
Function re.findall(pattern, string) returns a list of all matches of the pattern in the string. If the pattern includes one or more groups (as does ours), the list will consist of the matches groups - in our case everything between the quotes after a href. The second argument to re.findall is the Page we retrieved. The result is stored to Hrefs.
For instance, this is how we find all the pages to which our laboratory home page, http://www.ailab.si links:
To call Sicstus from Python, import module picstus which contains the high-level interface described here and, through importing the C++ part of the interface, the low-level structures described in a separate chapter. For simplicity, examples in this section will assume that you have loaded the module with from picstus import *. This is not a recommended practice in Python, though. If you load the module by import picstus, all the function and class names have to be prefixed by picstus.
Examples in this section will use the relations defined in the file bas.pl. Among other stuff, it contains three parenthood relations:
consult("bas.pl")
Function solutions(query_string, **flags) constructs a query from the given string which produces solutions one at a time. This is especially useful if the query has lots of solutions (or possibly an infinite number of them), since you can simply break out of the loop whenever you've found what you looked for.
Solutions are given as dictionaries with keys are variable names, and the solution, put into the values are Prolog's terms converted to native Python types. Thus, Prolog's integers, floats, atoms and lists are converted into Python's integers, floats, strings and lists. Compound terms are given as instances of Term (see the chapter on low-level structured). To have all terms stored as (unconverted) instances of Term, add convertToPython=False as a flag.
Underscores can be used as in Prolog.
The last two cases were somewhat special: the query has a solution, but the dictionary is empty since there are no variable values to be set. This is similar to one of the examples above, where we asked about parent(adam, abel). On the other hand, the following loop prints nothing, since Abel is not Adam's parent, so there are no solutions.
You can also use the function solutions to test whether any solutions exist. If there or no solutions, solutions returns False. If there are solutions and the query contains no named variables, it will return True.
In the third case - if there are solutions and there are named variables - the solutions returns a generator which generates the solutions one by one. When used in an if statement, such a generator evaluates as True, which is what we need, so the following code would work as intended.
Due to technical reasons, you should not store the result of solutions in any non-temporary variable, like this.
For as long as b remains alive, the query remains open. And even worse, when b is finally disposed, all the terms and queries created after b die with it. This is a rather technical issue which is explained in the section on the lifetime of queries, but you don't have to read it: just avoid storing the result of solutions, right?
There is also a function exists which returns True or False, telling whether there are any solutions or not. This function, described below, may be safer for unexperienced users.
When the number of solutions is known to be low enough and you want to get them all, call findall(query_string, **flags). If the number of solutions is huge (or infinite), findall will freeze and eventually crash.
Here are a few examples.
It may be instructive to see how findall is implemented. Apart from the two parameters you may not understand (a_terms and kwds), the function is trivial:
When you're only interested in whether a solution exists, use the function exists(query_string).
The function is actually trivial - it calls solutions and converts the result to a boolean:
There is an alternative way to calling the above functions: instead of solutions("parent(adam, abel)") you can replace the query string with the name of the predicate, and then add the list of terms as a second argument.
So what? What's the gain? First, sometimes (or, I can imagine, quite often) you will not query for some fixed elements but for something which is written in a Python variable. Say you got someone's name in a web form, stored it in a Python's variable name and now you'd like to get his/her parents. You'd do something like
solutions("parent(X, %s)" % name), right?
Of course, what you put inside the list don't need to be strings - you can also use integers and floats here, like
somepredicate(1, 2) is true or not.
But most importantly, besides strings, integers and floats, the list can include variables - terms which you have constructed before.
This is equivalent to calling solutions("parent(X, abel)") (this latter code actually transforms to the former). The difference is that in the above example we manually constructed the term and can thus reach it. For instance, like this
Remember that X is now a term, not a string (although it prints out as a string).
Why do we need name="X"? Unnamed terms (or terms whose names begin with underscores or are empty), behave exactly like names which (begin with) underscore. With a small differences: the identity of a term is not established by its name, but by the object. This has two consequences:
?- parent(_, _) would match all parent relations, the following would only give the persons which are their own parents.
Some users may prefer a more Prolog-like interface and type things like parent(X, abel) directly into Python. This is not possible since Python would see parent, X and abel as undefined variables. However, picstus has a trick to offer: you can write the query above by prefixing each atom or variable name with sp.
You can use it in the same way as solutions, either in a for loop
You can intermix the fake sp terms and atoms with strings, integers, floats and "true" atoms.
The first case is trivial. The second does exactly the same as the first: sp.Y in the first example actually calls a function which constructs Term(name="Y"). In this, second example, we can print out the value of Y instead of t.
The last example contains a trap: if we create an unnamed term and store it in Python variable Y this is, from picstus perspective, still an unnamed term, so the last predicate is equivalent to parent(_, abel). It will only find a single solution, store it in Y and return True. To get both solutions, we'd need to create Y as Y = Term(name="Y").
Finally, you can store some stuff in ordinary Python variables and use them for querying. This is also the main intention of the interface based on the "fake" predicates.
Note: If you load the picstus module by import picstus, you may still want to add from picstus import sp to avoid having to write things like picstus.sp.parent(picstus.sp._, picstus.sp.abel) which is rather cumbersome.
To see how this is implemented, just observe the classes __Faker and __PostponedAtom. The code is so short that any textual explanation would be longer (and it already is).
There's only one utility function at the moment.
filename. (It's not a big deal: it just executes picstus.Query("consult", [filename]).next_solution(), more of which you can learn below.)This part of the document describes the low level interface. It contains some technical details, which everybody except the potential future developers can safely skip; for inconvenience, it is printed in a smaller print.
The bulk of the interface is contained in four Python classes which represent the Sistus' atom, term, predicate and query.
Instances of Atom reference a Prolog atom. It's a rather poor object: you can construct it from a string and convert it back to string, like below.
Neither the interface nor Sicstus check for the syntactical correctness of the atom - you can start it with capital letters, include illegal characters... and you'll pay for it later.
The interface code ensures that Prolog's garbage collection does not dispose the atom by calling SP_register_atom when constructing an Atom and SP_unregister_atom when destructing it. In other words, Python keeps its reference count for the instance of Atom and only when it drops to zero it decreases the reference count in Sicstus.
Instances of Term represent references to terms in Sicstus: integers, floats, atoms, variables and compound terms.
Term's constructor accepts arguments of various types which determine the type of the term.
picstus.Atom, described above.Term constructor gets called recursively on the list elements).expression should be a Prolog expression, such as "parent(X, alice).", and the second argument is a list of variables (given as terms). If the expression contains no variables, an empty list must be given. (If the expression does not end with a period, the constructor will add it before passing the expression to Sicstus.)
Sicstus will parse the string, construct the corresponding Prolog structure (a tree of functors) and put the terms given in the list at the corresponding leaves. The length of the list should match the number of (different) variables: if the list is shorter, Sicstus will construct new variables, if it's longer, it will ignore the superfluous. The order in which the variables are assigned equals the order in which they appear in the expression.
We first constructed three variables; according to Python conventions we named them with lowercase first characters. Then we constructed a term from an expression containing three variables. Both instances of X are assigned a, Ys are assigned b and Zs are assigned c. Consider the expression a movie, variables are movie characters and the list [a, b, c] are the actors in order of appearance.
Say that a term t has been initialized as variable, t = Term() and is already included in a larger compound term. Now we want to assign it an integer. Writing t = Term(15) won't do the job, since it constructs a new term and assigns it to t instead of setting the value of the existing term. (t=15 is even worse, since it assigns a plain integer to t.)
To set the term to a certain value, use the method assign.
Arguments for assign are exactly the same as for the constructor explained above. (Actually, the constructor merely allocates the memory for the term and then forwards the call to assign.)
Prolog terms can be cast to Python types using Python's functions int, float, str and list.
The term must be of the right type for the cast, for instance, an atom cannot be cast into an integer.
Function list can be used for retrieving lists and for retrieving arguments of compound terms. List items or arguments can also be retrieved by indexing.
Arguments are numbered according to Python's indexing rules: the first argument has index 0.
Casting a compound to a term using str gives the functor and its arity. To get only the functor (as a string), use Term's method functor.
You can take a length of a list (which returns the number of elements) or of a compound term (which returns its arity).
Numeric terms support some basic arithmetic operations - it is possible to add a number to a term, multiply a term with a number, compute a logarithm of a term, or even a sum of a list (using the built-in function sum) if it contains nothing but numbers . However, all binary operations require that one of the operands is an ordinary Python number.The result is always a number, not a term.
Method Term.getType() returns the term's type: _picstus.VARIABLE (1), _picstus.INTEGER (2), _picstus.ATOM (3), _picstus.FLOAT (4) or _picstus.COMPUND (5).
To check for individual types, use methods is_variable(), is_integer(), is_atom(), is_float(), is_compound(), is_list(), is_atomic() and is_number(). Besides the basic types, which correspond to the types listed above, is_list() returns true if the term is a non-empty list (it is functor that matters, so empty list is an atom), is_atomic() returns true for atoms and numbers, and is_number() is true for integers and float.
The print statement or conversion to string by str function gives the natural presentation of terms - integers and floats are printed as numbers and atoms are printed as strings. Compound terms (including lists) are printed in the Prolog's usual functor/arity format. Variables are represented with underscore; this is essentially correct since the variables do not have any true names, their names are only tracked by the Prolog's interpreter.
Printing out the term in an interactive session, without using the print statement, gives something like <picstus.Term 15<, or <picstus.Term parent/2>.
Terms can be compared using the ordinary Python comparison operators, which function the same as the Prolog operators @<, == and @>.
Two terms can be unified, by calling a function unify (this is not a Term's method). The function returns true if unifications succeeds and false if it doesn't.
After this, X will equal eve and Y will equal john.
For a more complicated example, this also works as you would expect:
Terms are garbage collected at the end of a query. There are two types of queries that Picstus has to control. If Python is called from Prolog, this is a query and all the terms constructed during that call are destroyed when the call returns. Second, if Python constructs a query object (see below), all the terms constructed from that point on are destroyed when the query is closed.
This stack-like garbage collection, which is alien to Python, is controlled by Sicstus, to which the term object belong. There is no nice way around it, nor do we need one, since this handling of terms should come as natural to a Prolog programmer.
If Python code stored references to Prolog terms, which is destroyed by Prolog, the term will be marked in Python as dead. Trying to use a dead term will raise an exception.
Instances of class Predicate represent Sicstus predicates. The only method is have is a constructor, which needs to be given the name of the predicate, its arity and, optionally, a module. The name of the predicate and the module can be given as strings or atoms.
The following predicates are the same:
Passing atoms - if you already have them ready, not like in the above example - will save time because Sicstus will not have to look into the lookup table, yet the saving is minimal.
Predicates can be used in queries.
Queries function like this: we construct a query from a predicate and a list of terms, some of which may be variables. Then, we call next_solution which finds another solution and puts it into arguments. This goes on for as long as we want, or until we call cut or close, or the query object is destroyed in Python.
Predicate. Terms is a list of terms, some of which may be variables. The length of the list must equal the arity of the predicate, but can be omitted if if the predicate's arity is 0.user.Here are some examples.
This is a query which, when run (see below), would consult the file bas.pl.
Now follows the classic: the query for extracting all parent-child relations.
We have given the predicate as an atom now, so we had to add the module, too. Now the same with a predicate and with the child fixed:
If you add the module or change the list size in this example, the query construction will fail.
Well, this part is trivial: just keep calling next_solution until it returns false or you get bored. So, to find Eve's parents from the above example, we say
You can only call next_solution for the latest, innermost query. You can imagine queries put onto a stack, and you can also get the solutions from the latest.
Queries can be closed in four ways. First, when next_solution finds no (more) solutions, the corresponding query is closed. The query is also closed if it was created in Python code but is no longer referred to by any Python variable. You can explicitly close the query with the following two functions:
!. The current solution is retained in the arguments until backtracking into any enclosing query.!, fail: it discard the choices created since the query was opened and then backtracks into the query, throwing away any current solution.The query closed doesn't need to be the latest, innermost query. Closing any query also closes all the queries which were opened after ("inside") it.
As you have learned in the section about queries, when a query dies, so do all the terms created while the query was active.
As mentioned above, if you create a query B inside query A, and the close query A, query B is closed as well, and along with them, all the terms created while the queries were active. Since it is impossible to delete Python variables that may be referencing these queries, the corresponding Python wrappers are marked as dead. Trying to find the solutions of any closed query will raise an exception.
This may be sometimes confusing but there is no other way to couple the stack-based Sicstus' memory management with the heap-based Python's. A particularly nasty and hard-to-discover situations can occur when a certain variable name is reused in Python, like in the following case.
The first line constructs a query and the second gets the first solution (that is, the file is loaded). If we called next_solution for one more time, the query would fail and close, but since we have not the query is still open. Now we construct another query by calling _picstus.Query("parent(john, eve)", []). But when we assign it to p, the query to which p referred before is closed (since no other Python variables refer to it). Along with it, the second query is killed, too. So the final next_solution raises an exception.
To avoid the problem, we could call the first query until it fails, or close it manually, delete variable p (either with del p or by assigning it anything, e.g. p=42) or use another variable name for the second query.
to be written if there is any interest for it
Janez Demsar
Laboratory of Artificial Intelligence
Faculty of Computer and Information Science
University of Ljubljana, Slovenia