pyDatalog
pyDatalog copied to clipboard
Possible to use pyDatalog.predicate wrapped function in (inline) a rule body?
Hi all, I've been trying pyDatalog - seems quite nice.
I can create a pyDatalog.predicate as shown in the documentation:
from pyDatalog import pyDatalog
import pyDatalog.pyDatalog as dlog
pyDatalog.clear()
pyDatalog.create_terms('X, Y, p, f')
@pyDatalog.predicate()
def p(X,Y):
yield (1,2)
yield (2,3)
result = pyDatalog.ask('p(1,Y) & p(1,Y)')
print(result) # {(2,)}
But then...
body = p(1,Y) & p(1,Y) # Fail
f(X) <= body
gets:
body = p(1,Y) & p(1,Y) # Fail
TypeError: unsupported operand type(s) for &: 'generator' and 'generator'
Is it only possible to use this predicate via the parser?
Don't you have to add the arity to the function name ?:
def p2(X,Y):
Thank. Not sure. PyDatalog seems to allow either: We can create: "p2", and reference "p", or "p2".
@pyDatalog.predicate()
def p2(X,Y):
yield (1,2)
yield (2,3)
print(pyDatalog.ask('p(1,Y)')) # Docs: set([(1, 2)]), .. Actually prints: {(2,)}
print(pyDatalog.ask('p2(1,Y)')) # {(2,)}
print(pyDatalog.ask('p1(1,Y)')) # correct AttributeError
print(pyDatalog.ask('p3(1,Y)')) # correct AttributeError
I'm a little confused about why this works since @pyDatalog.predicate()
is implemented as:
def _predicate(func):
arity = len(inspect.getargspec(func)[0])
pyEngine.Python_resolvers[func.__name__ + '/' + str(arity)] = func
return func
... and doesn't attempt to remove 2
from func.__name__
.
Minor note, this actually prints: {(2,)}
here , but the docs say to expect: {(1,2)}
(https://sites.google.com/site/pydatalog/advanced-topics).
To answer the original point though, p(1,Y) & p(1,Y)
and p2(1,Y) & p2(1,Y)
both fail because pyDatalog.predicate
simply returns a generator function and python says no to <gen func> & <gen func>
. What could @pyDatalog.predicate
return to allow inline use of p2? A Pred
object? If I try:
@pyDatalog.predicate()
def p2(X,Y):
yield (1,2)
yield (2,3)
p2 = Pred("p2", 2)
q = p2(1,Y) & p2(1,Y) # TypeError: 'Pred' object is not callable
mytest(X,Y) <= q
I thought this might work because that's how 'p2' is represented internally during the string query:
I think pyDatalog.pyEngine.Pred
is simply an internal concept (? it inherits Interned
). It there an inline-mode / user-land equivalent that has __call__
defined? (which should return a Literal
?)
Updated the question -- really I'm looking to use a python-predicate resolver as a predicate in inline-rule.
Here's a quick fix:
@pyDatalog.predicate()
def p2(X,Y):
yield (1,2)
yield (2,3)
pyDatalog.create_terms('p')
pyDatalog.clear()
pyDatalog.create_terms('mytest, mytest2, X, Y')
# class PredInline(object):
# def __init__(self, predicate_name):
# self.predicate_name = predicate_name
#
# def __call__(self, *args):
# return pyDatalog.pyParser.Literal.make(predicate_name=self.predicate_name, terms=args)
@pyDatalog.predicate()
def p2(X,Y):
yield (1,2)
yield (2,3)
# Parsed query --- works
# print(pyDatalog.ask('p(1,Y) & p(1,Y)')) # {(2,)}
# print(pyDatalog.ask('p(X,Y) & p(X,Y)')) # {(1, 2), (2, 3)}
# pyDatalog.load("""
# mytest2(X,Y) <= p(X,Y) & p(X,Y)
# """)
# print(pyDatalog.ask('mytest2(1,Y) & mytest2(1,Y)')) # {(2,)}
# print(pyDatalog.ask('mytest2(X,Y) & mytest2(X,Y)')) # {(1, 2), (2, 3)}
pyDatalog.create_terms('p') # Make p available for inline use
#type(p) # pyParser.Term
mytest(X,Y) <= p(X,Y) & p(X,Y)
print((mytest(X,Y)).ask()) # [(2, 3), (1, 2)]
print(Y.data) # [3, 2]
print((mytest(1,Y)).ask()) # [(2,)]
print(Y.data) # [2]
... so should pyDatalog.predicate
return pyParser.Term("p")
?
Better:
pyDatalog.create_terms('mytest, mytest2, X, Y') # << no p
def pypredicate(func=None):
def _pypredicate(func):
name = func.__name__
pyDatalog._predicate(func)
# Drop number suffix?
# while name[-1].isdigit(): name =name[:-1]
return pyParser.Term(name)
if func is None: return _pypredicate
else: return _pypredicate(func)
@pypredicate()
def p(X,Y):
yield (1,2)
yield (2,3)
mytest(X,Y) <= p(X,Y) & p(X,Y)
print((mytest(X,Y)).ask()) # [(2, 3), (1, 2)]
print(Y.data) # [3, 2]
print((mytest(1,Y)).ask()) # [(2,)]
print(Y.data) # [2]
If I redefined pyDatalog.predicate
this way, would you accept a PR?
I tried to test that inline and parsed used of the predicate work the same. Almost, but I get:
assert type(pyDatalog.ask('p(1,2)')) == pyDatalog.pyParser.Answer
assert type(p(1,2).ask()) == list
... is it expected? I suspect it has more to do with the implementation of ask than my predicate decorator.
def predicate(func=None):
def _pypredicate(func):
name = func.__name__
pyDatalog._predicate(func)
# Drop number suffix?
# while name[-1].isdigit(): name =name[:-1]
return pyParser.Term(name)
if func is None: return _pypredicate
else: return _pypredicate(func)
def test_pypredicate_parsed():
pyDatalog.clear()
pyDatalog.create_terms('mytest, X, Y')
@predicate()
def p(X,Y):
yield (1,2)
yield (2,3)
# Does "p(...) & ..." and "... & p(...)" work?
assert type(pyDatalog.ask('p(1,2)')) == pyDatalog.pyParser.Answer
assert (pyDatalog.ask('p(1,2)')) == {()}
assert (pyDatalog.ask('p(1,Y)')) == {(2,)}
assert (pyDatalog.ask('p(1,Y) & p(1,Y)')) == {(2,)}
assert (pyDatalog.ask('p(X,Y) & p(X,Y)')) == {(1, 2), (2, 3)}
# In a rule...
pyDatalog.load("""
mytest(X,Y) <= p(X,Y) & p(X,Y)
""")
assert(pyDatalog.ask('mytest(1,2)&p(1,2)')) == {()}
assert (pyDatalog.ask('mytest(1,Y)&p(1,2)')) == {(2,)}
assert (pyDatalog.ask('mytest(X,Y)&p(X,Y)')) == {(1, 2), (2, 3)}
assert type(Y.data) == list
assert (Y.data)==[3,2]
assert (X.data)==[2,1]
def test_pypredicate_inline():
pyDatalog.clear()
pyDatalog.create_terms('mytest2, X, Y')
@predicate()
def p(X,Y):
yield (1,2)
yield (2,3)
# Does "p(...) & ..." and "... & p(...)" work?
assert type(p(1,2).ask()) == list
assert (p(1,2).ask()) == [()]
assert (p(1,Y).ask()) == [(2,)]
assert (p(1,Y) & p(1,Y)).ask() == [(2,)]
assert (p(X,Y) & p(X,Y)).ask() == [(2, 3), (1, 2)]
# In a rule...
mytest(X,Y) <= p(X,Y) & p(X,Y)
assert type((mytest(1,2)&p(1,2)).ask()) == list
assert (mytest(1,2)&p(1,2)).ask() == [()]
assert (mytest(1,Y)&p(1,2)).ask() == [(2,)]
assert X.data==[2,1]
assert (mytest(X,Y)&p(X,Y)).ask() == [(2, 3), (1, 2)]
assert type(Y.data) == list
assert (Y.data)==[3,2]
assert (X.data)==[2,1]
test_pypredicate_parsed()
test_pypredicate_inline()
Also, should:
def load(code):
"""loads the clauses contained in the code string """
stack = inspect.stack()
newglobals = {}
for key, value in stack[1][0].f_globals.items():
if hasattr(value, '_pyD_atomized'):
newglobals[key] = value
return pyParser.load(code, newglobals=newglobals)
... also load locals, not just globals? I'm having problems getting it to see the effect of pyDatalog.create_terms
or @predicate()
if called from inside the scope of a function body.