You can use the Python input()
function (input_raw()
in IPython) to request data from the user :
pyDatalog.create_terms('X,Y, quacks, input')
quacks(X) <= (input('Does a '+X+' quack ? (Y/N) ')=='Y')
Please note that the prompt string contains a variable, X. Without it, e.g. in (X==input('Enter your name : ')
, the user would be prompted when the clause is declared, not when it is used during query evaluation.
In Python 3, you can use the print()
function to show a result on the console:
pyDatalog.create_terms('print')
ok(X) <= (0 < X) & (Y==print(X))
In Python 2, print
is a statement, not a function. So, you would need to create your own print_()
function:
def print_(x):
print(x)
pyDatalog.create_terms('print_')
ok(X) <= (0 < X) & (Y==print_(X))
To get a full trace of pyDatalog's reasoning, add the following instructions before a query:
import logging
from pyDatalog import pyEngine
pyEngine.Logging = True
logging.basicConfig(level=logging.INFO)
A Python program may start several threads. Each thread should have these statements to initialize pyDatalog :
from pyDatalog import pyDatalog, Logic
Logic() # initializes the pyDatalog engine
Each thread can then define its own set of clauses, and run queries against them. See the ThreadSafe.py example.
Alternatively, threads can share the same set of clauses, by following these steps :
Logic(True)
object to the threads it creates. Logic(arg)
, where arg
is the Logic(True)
object it received from the calling thread.Please note that, once passed, the set of clauses should not be changed (such changes are not thread safe).
Finally, a program may switch from one set of clauses to another :
Logic() # creates an empty set of clauses for use in the current thread
# add first set of clauses here
first = Logic(True) # save the current set of clauses in variable 'first'
Logic() # first is not affected by this statement
# define the second set of clauses here
second = Logic(True) # save it for later use
Logic(first) # now use first in the current thread
# queries will now run against the first set of rules
See the Multi-Model.py example for illustration.
Some applications need to construct datalog statements dynamically, i.e. at run-time. The following interface can then be used.
A Python program can assert fact in the in-memory Datalog database using assert_fact()
. The first argument is the name of the predicate :
from pyDatalog.pyDatalog import assert_fact, load, ask
# + parent(bill, 'John Adams')
assert_fact('parent', 'bill','John Adams')
A python program calls the load function to add dynamic clauses :
# specify what an ancestor is
load("""
ancestor(X,Y) <= parent(X,Y)
ancestor(X,Y) <= parent(X,Z) & ancestor(Z,Y)
""")
It can now query the database of facts :
# prints a set with one element : the ('bill', 'John Adams') tuple
print(ask('parent(bill,X)'))
A predicate such as p(X,Y)
or (a.p[X]==Y)
can be resolved using a custom-made python method, instead of using logical clauses. Such python resolvers can be used to implement connectors to non-relational databases, or to improve the speed of specific clauses. It has precedence over (and replaces) any clauses in the pyDatalog knowledge base.
Unprefixed resolvers
An unprefixed resolver is used to resolve an unprefixed predicate. It is defined using the @pyDatalog.predicate()
decorator, and by adding the arity to the predicate name:
@pyDatalog.predicate()
def p2(X,Y):
yield (1,2)
yield (2,3)
print(pyDatalog.ask('p(1,Y)')) # prints == set([(1, 2)])
This simple resolver returns all possible results, which pyDatalog further filters to satisfy the query. Often, the resolver will use the value of its X and Y arguments to return a smaller, (if possible exact) set of results. Each argument has a .is_const()
method : it returns True
if it is a constant, and False
if it is an unbound variable. If it is a constant, the value can be obtained with .id
(e.g. X.id
).
Prefixed resolvers
A prefixed resolver is defined in a class. The following resolver is equivalent, and replaces, Employee.salary_class[X] = Employee.salary[X]//1000.
class Employee(pyDatalog.Mixin):
@classmethod
def _pyD_salary_class2(cls, X, Y):
if X.is_const():
yield (X.id, X.id.salary//1000)
else:
for X in pyDatalog.metaMixin.__refs__[cls]:
Y1 = X.salary // 1000
if not Y.is_const() or Y1==Y.id:
yield (X, Y1)
This method will be called to resolve Employee.salary_class(X,Y)
and (Employee.salary_class[X]==Y)
queries. Each resolver must have the same number of arguments as the arity of its predicate. Note that the arity is appended to the end of the method name.
The list of instances in a class cls
is available in pyDatalog.metaMixin.__refs__[cls] . For SQLAlchemy classes, queries can be run on cls.session
.
A class can also have (or inherit) a generic resolver, called _pyD_query
. Its arguments are the predicate name and its arguments.
# generic resolver
class Employee(pyDatalog.Mixin):
@classmethod
def _pyD_query(cls, pred_name, args):
if pred_name == '
Employee.salary_class':
if args[0].is_const():
yield (args[0].id, args[0].id.salary//1000)
else:
for X in pyDatalog.metaMixin.__refs__[cls]:
Y1 = X.salary // 1000
yield (X,Y1)
else:
raise AttributeError
The best performance is obtained with pypy, the JIT compiler of python. Please note that:
A pyDatalog program will never beat the same program written in pure python, but:
Performance tip: