Some of Python's powerful meta-programming features are used to enable writing Python functions which include Prolog-like statements. Such functions load a Prolog-like database. When coupled with a suitable inference engine for logic databases, this is a way to add logical programming -- the last unsupported major paradigm -- to Python. Start at the bottom of the code for an example of the enabled syntax.
| Python |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 | #
# Pythologic.py
#
# Add logic programming (Prolog) syntax into Python.
#
# (c) 2004 Shai Berger
#
import string
class Struct:
def __init__(self, database, head, subs):
"""
The head and subs are essential - what makes this struct.
The database should only be used while structs are constructed,
and later removed.
"""
self.database = database
self.head = head
self.subs = subs
def __pos__(self):
"""
unary + means insert into database as fact
"""
self.database.add_fact(self)
def __lshift__(self, requisites):
"""
The ideal is
consequent(args) << cond1(args1),...
for now we must do with
consequent(args) << (cond1(args1),...)
"""
self.database.add_conditional(self, requisites)
def __str__(self):
subs = map (str, self.subs)
return str(self.head) + "(" + string.join(subs,',') + ")"
class Symbol:
def __init__ (self, name, database):
self.name = name
self.database = database
def __call__ (self, *args):
return Struct(self.database, self, args)
def __str__(self):
return self.name
class Constant(Symbol):
"""
A constant is a name. Its value is its name too.
"""
def value(self): return self.name
class Variable(Symbol):
def __str__(self):
return "?"+self.name
def symbol(name, database):
if (name[0] in string.uppercase):
return Variable(name,database)
else:
return Constant(name, database)
class Database:
def __init__(self):
self.facts = []
self.conditionals = []
def add_fact(self, fact):
self.facts.append(fact)
def add_conditional(self,head,requisites):
# Older Python
# if not(isinstance(requisites, type([]))):
# More modern
if not(isinstance(requisites, list)):
requisites = [requisites]
self.conditionals.append((head,requisites))
def prt(self):
"""
Print the database in somewhat readable (prolog) form
"""
for f in self.facts: print f, "."
for (h,r) in self.conditionals:
print h, ":-", string.join(map(str,r), " , "), "."
def consult(self, func):
"""
Include definitions from func into database
"""
try:
code = func.func_code
except:
raise TypeError, "function or method argument expected"
names = code.co_names
locally_defined = code.co_varnames
globally_defined = func.func_globals.keys()
defined = locally_defined+tuple(globally_defined)
# Python < 2.0
# undefined = filter (lambda n,d=defined: n not in d, names)
# Modern Python
undefined = [name for name in names if name not in defined]
# Generate the new global environment for the function;
# to the old environment, add definitions for all undefined
# symbols, which relate to this database (self). When the
# symbols are operated on in the function, they will add
# facts and conditionals to the database.
newglobals = func.func_globals.copy()
for name in undefined:
newglobals[name] = symbol(name, self)
exec code in newglobals
def consult_and_transform(self, func):
"""
A helper for decorator implementation
"""
self.consult(func)
return LogicalFunction(self, func)
class LogicalFunction:
"""
This class replaces a logical function once it has
been consulted, to avoid erroneous use
"""
def __init__(self, database, func):
self.database=database
self.logical_function=func
def __call__(self):
raise TypeError, "Logical functions are not really callable"
def logical(database):
"""
A decorator for logical functions
"""
return database.consult_and_transform
if __name__ == "__main__":
db = Database()
global_var = ["known", "fact"]
print "Defining a logical function...",
@logical(db)
def prolog_func():
# Undefined names are given logical meaning.
#
# Following Prolog, if the name starts with an uppercase letter,
# it is a logical variable (will be printed with a prefixed "?"
# to clarify), otherwise it is a logical constant.
#
# unary plus defines a fact
+ farmer(moshe)
+ donkey(eeyore)
# left-shift defines a conditional (this is an encoding
# of the famous "donkey sentence" studied a lot in natural
# language semantics: "If a farmer has a donkey, he beats it").
beats(X,Y) << [ farmer(X), donkey(Y), owns(X,Y) ]
# Define local variables -- regular Python
x = "'local value of x'"; y = 17
# Local and global variables (as well as other expressions)
# can participate in facts and conditionals
+ globally(global_var)
equal("x","y") << equal(x,y)
# For Pre-2.4, replace the @logical decorator with this line:
# prolog_func = db.consult_and_transform(prolog_func)
print "Done."
print "Definition has already updated the database as follows:"
print
db.prt()
print
print "Trying to call the logical function raises an error:"
print
prolog_func()
|
Discussion
Python is widely acclaimed for supporting many programming paradigms; you can write procedural code, object oriented code, functional code, and thanks to metaclasses, even aspect oriented programming is not hard. However, Python has no support for the logical programming paradigm; this recipe aims to bring Python a little closer there.
Start at the bottom of the source. The goal of this exercise is to enable the writing of functions like prolog_func(), where a collection of facts and rules can be written in a language reminiscent of Prolog and First-Order Logic. These facts and rules are collected into a database, where an inference engine can later use them to answer queries (the inference engine and query interface are out of scope here, and left out).
Putting logical inference code into Python has been done before, e.g. http://christophe.delord.free.fr/en/pylog/. But Pylog makes it relatively hard to use Python objects from the Prolog code (you can, but not using Prolog syntax). This is the problem Pythologic solves.
The "magic" is divided between Database.consult(), which turns all undefined names in the function to logical symbols, and the overloaded operators in the Struct and Symbol classes.
This recipe has some problems: First of all, it is wildly unpythonic, in its abusive overhaul of the function semantics. At a more detailed level, the function is called upon definition (which would typically mean during import), which may cause surprising problems with respect to normal Python code in it. The function is called with a specially-constructed global environment, which means assignments to globals will not take effect (this is quite easy to fix, but would clutter the code somewhat). For perfect integration, Python callables appearing in Prolog rules should not be called until the rules are evaluated (and then, should be called with values obtained from logical variables). The recipe does not support this -- this would require some pretty deep code transformations, and this is a proof-of-concept only. If anyone chooses to go there, I would also suggest removing the unary plus requirement.
Still, I think it is an interesting, and (if I may say so myself) thought-provoking example of how far a little meta-programming can take you.
Although this recipe uses a @decorator, and therefore applies only to Python 2.4, it is very easy to follow instructions in the code to make it work with older Pythons; this should work even down to 1.5.2, though I haven't tested it.


Comments
Wow! Python kicks ass!
I think you meant... If Python has an ass, he kicks it!
Reasonable Python. Hello everyone,
I know that this is a pretty old recipe but I just wanted to draw your attention to the Reasonable Python projects which uses some of the concepts stated above at (http://reasonablepy.sf.net). I'm trying to use F-Logic in Python which is a kind of object-oriented logic programming.
Best regards
Sign in to comment