PEP writer: fix unbound local.
[docutils/kirr.git] / sandbox / rstdiff / studies / hashable.py
blobc3e2da0606521839dcceb0a0bd4d06b19e586bdf
1 # A study to check how a Docutils node can be made hashable
3 from docutils.nodes import Node
5 __docformat__ = 'reStructuredText'
7 class HashableDescriptor(object):
8 """A descriptor to plug into a class to be made hashable."""
10 """Name of function to override."""
11 override = None
12 """Arity of function to override."""
13 arity = None
14 hashableImpl = None
15 debug = False
17 def __init__(self, override, arity, hashableImpl):
18 """Initialize a descriptor for function `override` with `arity` using
19 `hashableImpl`."""
20 self.override = override
21 self.arity = arity
22 self.hashableImpl = hashableImpl
24 def __get__(self, instance, owner):
25 """Implements the descriptor protocol. Returns a function to use on
26 `instance`."""
27 if self.debug:
28 print('__get__ called on ' + owner.__name__ + ' by '
29 + self.override)
30 try:
31 fct = self.hashableImpl.getFunction(self.override)
32 except:
33 if self.debug:
34 print('***Exception***')
35 raise AttributeError('Can not access method %r'
36 % ( self.override, ))
37 if self.arity == 0:
38 return lambda: fct(instance)
39 elif self.arity == 1:
40 return lambda other: fct(instance, other)
41 else:
42 raise AttributeError('Can not create function for %r with arity %d'
43 % ( self.override, self.arity, ))
45 class HashableImpl(object):
46 """Implements hashability and reflexive equality for a class."""
48 debug = False
50 def __init__(self, cls):
51 """Set methods to implement equality in `cls`."""
52 setattr(cls, '__hash__', HashableDescriptor('__hash__', 0, self))
53 setattr(cls, '__eq__', HashableDescriptor('__eq__', 1, self))
54 setattr(cls, '__ne__', HashableDescriptor('__ne__', 1, self))
56 def getFunction(self, name):
57 """Return the function named `name`."""
58 # Get the function from the *real* object
59 return getattr(self, 'impl' + name)
61 def impl__hash__(self, this):
62 """Return `this.__hash__`(). Derived classes must override this."""
63 if self.debug:
64 print('impl__hash__ called')
65 return id(this)
67 def impl__eq__(self, this, other):
68 """Return `this.__eq__`(other). Derived classes must override this."""
69 if self.debug:
70 print('impl__eq__ called')
71 return id(this) == id(other)
73 def impl__ne__(self, this, other):
74 """Return `this.__ne__`(other). Derived classes must override this."""
75 if self.debug:
76 print('impl__ne__ called')
77 return not self.impl__eq__(this, other)
79 class HashableNodeImpl(HashableImpl):
80 """An abstract implementation of `HashableImpl` for nodes in a tree.
81 Allows switching between of root only comparison and deep
82 comparison."""
84 """Consider only the root node (or include the children)."""
85 _rootOnly = False
86 """Stack for `_rootOnly`"""
87 __rootOnlies = [ ]
89 def __init__(self, cls):
90 HashableImpl.__init__(self, cls)
92 def pushRootOnly(self, newRootOnly):
93 """Set `newRootOnly` as new `rootOnly` value. If ``True`` then only
94 information in the root is considered."""
95 self.__rootOnlies.append(self._rootOnly)
96 self._rootOnly = newRootOnly
98 def popRootOnly(self):
99 """Pop and return last `rootOnly` value."""
100 self._rootOnly = self.__rootOnlies.pop()
102 def impl__hash__(self, node):
103 """Returns the hash for node `node`. Subclasses may override this but
104 overriding `rootHash` and `childrenHash` may make more sense."""
105 rootHash = self.rootHash(node)
106 if self._rootOnly:
107 return rootHash
108 else:
109 return rootHash + self.childrenHash(node)
111 def impl__eq__(self, node, other):
112 """Returns equality between `node` and an `other` node. Subclasses may
113 override this but overriding `rootEq` and `childrenEq` may
114 make more sense."""
115 if not self.rootEq(node, other):
116 return False
117 if self._rootOnly:
118 return True
119 return self.childrenEq(node, other)
121 def childrenHash(self, node):
122 """Return a hash for the children only. Subclasses may override
123 this but overriding `childHash` may make more sense."""
124 return reduce(lambda x, y: x + y,
125 [ self.childHash(child)
126 for child in self.getChildren(node) ], 0)
128 def childrenEq(self, node, other):
129 """Returns children equality of `node` and an `other` node. ``True``
130 if the children of the two nodes are equal without considering
131 the root. Subclasses may override this but overriding
132 `childEq` may make more sense."""
133 nodeChildren = self.getChildren(node)
134 otherChildren = self.getChildren(other)
135 if len(nodeChildren) != len(otherChildren):
136 return False
137 for i in xrange(len(nodeChildren)):
138 if not self.childEq(nodeChildren[i], otherChildren[i]):
139 return False
140 return True
142 def rootHash(self, node):
143 """Return a hash for the root only. Subclasses must override
144 this."""
145 raise NotImplementedError()
147 def childHash(self, node):
148 """Return a hash for the node as a child. Subclasses must override
149 this."""
150 raise NotImplementedError()
152 def rootEq(self, node, other):
153 """Returns root equality of `node` and an `other` node. ``True`` if
154 the two nodes as roots are equal without considering their
155 children. This should be true if one node can be replaced by
156 the other and all changes can be represented without changing
157 the node itself. Subclasses must override this."""
158 raise NotImplementedError()
160 def childEq(self, node, other):
161 """Returns equality of `node` and an `other` node as children.
162 ``True`` if the child features of the two nodes are equal
163 without considering the root. Subclasses must override
164 this."""
165 raise NotImplementedError()
167 def getChildren(self, node):
168 """Return the children of `node` as a list. Subclasses must override
169 this."""
170 raise NotImplementedError()
172 class HashableDocutilsNodeImpl(HashableImpl):
173 """Implements equality for a docutils `Node`."""
175 def __init__(self):
176 super(self.__class__, self).__init__(Node)
178 if __name__ == '__main__':
179 hashableImpl = HashableDocutilsNodeImpl()
180 hashableImpl.debug = True
182 n1 = Node()
183 n2 = Node()
184 print(n1 == n1)
185 print(n1 == n2)
186 print(n1 != n2)
187 h = { n1: 'bla' }