In a little Prolog interpreter I'm writing I needed a simple and concise way to create Var-objects and also store them in a mapping varname -> Var-object representing the scope of the current Prolog rule. Also, anonymous Prolog variables ("_") should not go into the mapping and should be renamed to _<unique-number>. I came up with this:
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 | from collections import defaultdict
from itertools import count
class Var(object):
def __init__(self, name):
self.name = name
def __repr__(self):
return "Var(%s)" % self.name
class vardict(defaultdict):
def __init__(self, *args, **kwargs):
super(vardict, self).__init__(Var, *args, **kwargs)
def __missing__(self, key, unique=count()):
if self.default_factory is None:
raise KeyError(key)
if key == "_":
return self.default_factory(key + str(next(unique)))
self[key] = value = self.default_factory(key)
return value
if __name__ == "__main__":
vdict = vardict()
vlist = []
vlist.append(vdict["First"])
vlist.append(vdict["Second"])
vlist.append(vdict["_"])
vlist.append(vdict["First"])
vlist.append(vdict["Second"])
vlist.append(vdict["_"])
vlist.sort()
print
for key, value in vdict.items():
print key, ":", value
print
for each in vlist:
print id(each), ":", each
|
vardict follows the definition of defaultdict quite closely, except:
1) it's __init__-method doesn't take a default_factory argument,
2) "_" is not inserted into the dict and also renamed to _<unique-number>,
3) __missing__'s key parameter is passed on to the default_factory.
Point 2) ensures that every anoymous variable gets renamed and instantiated uniquely, whereas other variables are managed as singletons in the current Prolog rule's namespace.
Point 3) shows the main motivation for this recipe. I wanted to add newly created Var-objects into a dictionary and defaultdict seemed almost useful for this task, except Var-objects needed to know their own name, and that was not possible as-is. But since default_factory is only called from within __missing__, nothing inhibits us from changing it to accept an argument if __missing__ is changed as well.