The janus API for Prolog Calling Python

Contents

This chapter describes each predicate in the janus Prolog calling Python API. Examples are provided for some of the predicates, many more examples can be found in Janus Examples for Prolog Calling Python .

Preliminary Definitions

We introduce some terminology and calling conventions through a short series of examples.

janus GoalTerms

Syntactically, a janus GoalTerm is either an atom or a compound term. If it is a compound term, any argument to a goal whose outer functor is =/2 is treated as a Python keyword argument. As in Python any keyword argument must occur after all positional arguments.

If GoalTerm is an atom it corresponds to a attribute call; if it is a compound term it corresponds to function or method call.

janus GoalTerm Example

py_func(jns_rdflib,rdflib_write_file(Triples,'out.ttl',format=turtle),Return).

calls the Python function jns_rdflib.rdflib_write_file() to write Triples, a list of triples in Prolog format, to the file new_sample.ttl using the RDF turtle (ttl) format. For the Python function jns_rdf.rdflib_write_file(), format is a keyword, and turtle is an allowed value for that keyword.

Keyword arguments are allowed in py_call/[2,3,4], py_func/[3,4], py_dot/[3,4], and py_iter/[2,3,4].

janus GoalSequences

janus GoalSequence: Introductory Example

Consider a file dog.py such as the following (modified from the Python documentation)

--------------    --------------    --------------
current_dog = ''
latest_trick = []

class Dog:
    tricks = []

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

    def add_trick(self, trick):
    global latest_trick
        self.tricks.append(trick)
    latest_trick = trick
    return self.tricks

--------------    --------------    --------------

We can interact with this class as follows. Executing the either:

?- py_call(dog:'Dog'('Fido'), MyDog).

or

?- py_func(dog,'Dog'('Fido'), MyDog).

unifies Dog with a reference to the Python object created by initializing an instance of the Dog class via: dog.Dog('Fido'). Next, suppose we extend both goals.

?- py_call(dog:'Dog'('Fido'), MyDog),
   py_call(MyDog:add_trick(roll_over),Tricks).

and

?- py_func(dog,'Dog'('Fido'), MyDog),
   py_dot(MyDog,add_trick(roll_over),Tricks).

both goals not only create the dog MyDog but add a trick to the repetoire of MyDog and return the current tricks of MyDog. The second goal shows in each case illustrates how to make a method call to a Python object reference.

Suppose that there were no need to retain the dog object in Prolog, but simply to update the global variables of the module dog.py The above conjunctive goal could be rewritten as:

?- py_call(dog:'Dog'('Fido'):add_trick(roll_over), Tricks).

Finally, consider checking one of the attributes of the module dog. In this case the goals

?- py_call(dog:current_dog,Name)

and

?- py_dot(dog,current_dog,Name)

both unifiy Name with the dog.current_dog and illustrate how to obtain an attribute of a Python object reference.

janus GoalSequence: Definition

Formally, a janus GoalSequence is defined inductively as:

  • Target:GoalTerm where Target is either an atom corresponding to a Python Module name, or a Python object reference; and GoalTerm is defined as in 1.1.

  • GoalSequence:GoalTerm where TargetSequence is a GoalSequence such that

    py_call(GoalSequence,Return)

    unifies Return with an atom corresponding to a module name or a Python object reference.

The Pseudo-Module builtins

In addition to using Python module names, Python built-in functions can be called using the “pseudo-module” builtins. For instance

py_func(builtins, float(’+1E6’),F).

produces the expected result:

F = 1000000.0

The pseudo-module builtins is allowed in the first argument of py_call/[2,3,4], py_func/[3,4], py_dot/[3,4], and epy_iter/[2,3,4].

Python Errors

A python_error is a term of the form:

error(python_error(ErrorType,ErrorValue),OtherInformation)

where - ErrorType is the Python type name for the exception type - ErrorValue is a translation into Prolog of the Python Object associated with the error

The Main Predicates to Call Python

Predicates py_func/3 py_func/4

  • py_func(+Module, +Function, ?Return)
  • py_func(+Module, +Function, ?Return, +Options)

Ensures that the Python module Module is loaded, and calls Module.Function unifying the return of Function with Return.

Function is a Goal Term (1.1), while Module is an atom representing a Python module. If Module has not already been loaded, it will be automatically loaded during the call. Python modules are searched for in the paths maintained in Python’s sys.path list; these Python paths can be queried from Prolog via py_lib_dir/1 and modified via py_add_lib_dir/1.

Prolog Options

py_object(true) This option returns most Python data structures as object references, so that attributes of those data structures can be queried if needed. The only data not returned as object references are

  • Objects of boolean type

  • Objects of none type

  • Objects of exactly the class long, float or string. Objects that are proper subclasses of these types are returned as object references.

Error Cases

  • py_func/4 is called with Module, Function or Options uninstantiated
    • instantiation_error
  • Module is not a Prolog atom:
    • type_error
  • Module cannot be found in the current Python search paths or is otherwise unloadable:
    • python_error
  • Function is not a callable term
    • type_error
  • Function does not correspond to a Python function in Module or is otherwise uncallable
    • python_error
  • Options contains an improper element or combination of elements
    • domain_error
  • Function contains a Prolog term that cannot be translated into Python:
    • domain_error

Any other error thrown by Python during the call should be caught and thrown as a Prolog python_error

Predicates py_dot/3, py_dot/4

  • py_dot(+Target, +MethAttr, ?Return)
  • py_dot(+Target, +MethAttr, ?Return, +Options)

Applies a method to Target or obtains an attribute value for Target. More specifically, Target is a either Python object reference or an atom representing a Python module. MethAttris a Goal Term (1.1) that can either be a Prolog compound term or a Prolog atom:

  • If MethAttr is a Prolog compound term corresponding to a Python method for Target, the method is called and its return unified with Return.

  • If MethAttr is a Prolog atom corresponding to the name of an attribute of Target, the attribute value (for Target) is accessed and unified with Return.

Options is as described for py_func/[3,4] (2.1)

Error Cases

  • py_dot/4 is called with Target, MethAttr or Options uninstantiated:
    • instantiation_error
  • Target is not a Prolog atom or Python object reference
    • type_error
  • MethAttr is not a callable term or atom.
    • type_error
  • MethAttr does not correspond to a Python method or attribute for Target
    • python_error
  • Options contains an improper element, or combination of elements.
    • domain_error
  • MethAttr contains a Prolog term that cannot be translated into Python:
    • domain_error

Any other error thrown by Python during the call should be caught and thrown as a Prolog python_error.

Predicate py_setattr/3

  • py_setattr(+Target, +Attr, +Value)

If Target is a module or an object, this command is equivalent to the Python

setattr(Target, Attr, Value)

or in Prolog

py_setattr(Target, Name, Value) :-
    py_call(Target, Obj, [py_object(true)]),
    py_call(setattr(Obj, Name, Value)).

Error Cases

  • py_setattr/3 is called with Target, Attr or Value uninstantiated:
    • instantiation_error
  • Target is not a Python object reference or Prolog atom
    • type_error
  • Name is not an atom.
    • type_error
  • Value contains a Prolog term that cannot be translated into Python:
    • domain_error

Any other error thrown by Python during the call should be caught and thrown as a Prolog python_error.

Predicates py_iter/2, py_iter/3

  • py_iter(+Iterator, ?Value)
  • py_iter(+Iterator, ?Value,+Options)

In py_iter/[2,3] Iterator can either be a reference to a Python iterator object or a janus GoalSequene (1.2) that produces a reference to a Python iterator object. In either case, the first value of the iterator will be returned by the goal and subsequence values returned via backtracking (cf. Example <a href=“#jns-examp:lazy-ret” data-reference-type=“ref” py_iter/[2,3] when there are no values left for Iterator.

Options are as described in py_func/[3,4]

Examples

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

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

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

Error Cases

  • py_iter/[2,3] is called with Iterator nonground or Options uninstantiated
    • instantiation_error
  • The GoalSequence Iterator contains a goal Target:GoalTerm where
    • Target is neither a Prolog atom nor a reference to a Python iterator object:
      • type_error
    • Target is an atom representing a Python module that cannot be found in the current Python search paths or is otherwise unloadable:
      • python_error
    • GoalTerm is neither a callable term nor an atom
      • type_error
    • Function does not correspond to a Python function in Module or is otherwise uncallable
      • python_error
    • GoalTerm contains a Prolog term that cannot be translated into Python:
      • domain_error
  • Options contains an improper element, or combination of elements
    • domain_error

Predicates py_call/1, py_call/2, py_call/3

  • py_call(+GoalSequence)
  • py_call(+GoalSequence, ?Return)
  • py_call(+GoalSequence, ?Return, +Options)

Execute GoalSequence in Python and and unify the result with Return (in py_call/[2,3]). GoalSequence is a janus GoalSequence as described in 1.2. Options is as described for py_func/[3,4]

Error Cases

  • py_call/[2,3,4] is called with a nonground GoalSequene or Options uninstantiated
    • instantiation_error
  • GoalSequence contains a goal Target:GoalTerm where
    • Target is neither a Prolog atom nor a reference to a Python object:
      • type_error
    • Target is an atom representing a Python module that cannot be found in the current Python search paths or is otherwise unloadable:
      • python_error
    • GoalTerm is neither a callable term nor an atom
      • type_error
    • Function does not correspond to a Python function in Module or is otherwise uncallable
      • python_error
    • GoalTerm contains a Prolog term that cannot be translated into Python:
      • domain_error
  • Options contains an improper element, or combination of elements
    • domain_error

Python Reflection Predicates

Predicate py_type/2

  • py_type(+ObjRef, ?Type)
    True when the Type unifies with the name of the type of ObjRef represented as a Prolog atom. This is the same as

    type(ObjRef).__name__

in Python.

Error Cases
- py_type/2 is called with a nonground ObjRef - instantiation_error - py_type/2is called withTypeneither a variable nor an atom -type_error`

Predicate py_isinstance/2

  • py_isinstance(+ObjRef, +Type)
    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. Type must be instantiated either to a term Module:Type or to an atom to refer to a built-in type.

Error Cases
- ObjRef or Type is uninstantiaed at call - instantiation_error - ObjRef is not a Python object reference, or Type is not an atom - type_error

Predicate py_module_exists/1

  • py_module_exists(+Module)
    True if the atom Module is a module loaded into Python or loadable into Python. The predicate calls the Python

    importlib.util.find_spec(Module)

Error Cases
- Module is uninstantiaed at call - instantiation_error - `Module is not an atom - type_error

Other janus predicates

Predicate py_is_object/1

  • py_is_object(+ObjRef)
    Succeeds if ObjRef is a Python object reference and fails otherwise. Different Prologs that implement Janus will have different representations of Python object references, so this predicate should be used to determine whether a term is a Python object reference.

Predicate py_free/1

  • py_free(+ObjRef)

In general when janus bi-translates between Python objects and Prolog terms it performs a copy: this has the advantage that each system can perform its own memory management independently of the other. The exception is when a reference to a Python object is passed to Prolog. In this case, Python must explicitly be told that the Python object can be reclaimed, and this is done through py_free/1.

Calling py_free(ObjRef) causes Python to decrement the reference count of ObjRef. This may not actually delete the object because the object may have references inside Python.

On some implementations, reclaiming references from Prolog to Python objects is done by the Prolog garbage collector. For portability, py_free/1 must be called exactly once on each object reference obtained from Python. Further usage of the object reference may cause undefined behavior.

Predicates py_pp/1, py_pp/2, py_pp/3

  • py_pp(+Stream, +Term,+Options)
  • py_pp(+Stream, +Term)
  • py_pp(+Term)

Pretty prints a janus Python term Term to . The implementation and behavior of these predicates are system-dependent.

Common Error Cases

  • Stream. Term or Options is not instantiated
    • instantiation_error
  • Stream_or_alias is neither a variable, a stream term, nor an alias; or Options contains an improper element
    • domain_error

Predicates py_add_lib_dir/1, py_add_lib_dir/2

  • py_add_lib_dir(+Dir)

  • py_add_lib_dir(+Dir, +Where)

    Add a directory to the Python module search path (sys.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.

Error Cases

  • Dir or Where is uninstantiated at call
    • instantiation error
  • Dir or Where are not instantiated to an atom
    • type error
  • Where is not first or last
    • domain_error

Predicate py_lib_dirs/1

  • py_lib_dirs(?Path)

This convenience and compatibility predicate unifies Path with a list of the current Python library directories as obtained from sys.path. There are no associated error cases.

Predicate values/3

  • values(+Dict, +Path, ?Val)
    Convenience and compatibility predicate to obtain a value from a (possibly nested) janus dictionary term . The goal

    values(D,key1,V)

is equivalent to the Python expression D[key1] while

values(D,[key1,key2,key3],V)

is equivalent to the Python expression

D[key1][key2][key3]

There are no error conditions associated with this predicate.

Predicates items/2, key/2, keys/2

These convenience predicates provide a Prolog equivalent of some of Python’s dictionary access routines.

  • keys(+Dict, ?Keys)

    True when Keys is a list of keys that appear in Dict.

  • key(+Dict, ?Key)

    True when Key is a key in Dict. Backtracking enumerates all known keys.

  • items(+Dict, ?Items)

    True when Items is a list of Key:Value mappings that appear in Dict.



The Prolog Implementers Forum is a part of the "All Things Prolog" online Prolog community, an initiative of the Association for Logic Programming stemming from the Year of Prolog activities.