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."""
12 """Arity of function to override."""
17 def __init__(self
, override
, arity
, hashableImpl
):
18 """Initialize a descriptor for function `override` with `arity` using
20 self
.override
= override
22 self
.hashableImpl
= hashableImpl
24 def __get__(self
, instance
, owner
):
25 """Implements the descriptor protocol. Returns a function to use on
28 print('__get__ called on ' + owner
.__name
__ + ' by '
31 fct
= self
.hashableImpl
.getFunction(self
.override
)
34 print('***Exception***')
35 raise AttributeError('Can not access method %r'
38 return lambda: fct(instance
)
40 return lambda other
: fct(instance
, other
)
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."""
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."""
64 print('impl__hash__ called')
67 def impl__eq__(self
, this
, other
):
68 """Return `this.__eq__`(other). Derived classes must override this."""
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."""
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
84 """Consider only the root node (or include the children)."""
86 """Stack for `_rootOnly`"""
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
)
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
115 if not self
.rootEq(node
, other
):
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
):
137 for i
in xrange(len(nodeChildren
)):
138 if not self
.childEq(nodeChildren
[i
], otherChildren
[i
]):
142 def rootHash(self
, node
):
143 """Return a hash for the root only. Subclasses must override
145 raise NotImplementedError()
147 def childHash(self
, node
):
148 """Return a hash for the node as a child. Subclasses must override
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
165 raise NotImplementedError()
167 def getChildren(self
, node
):
168 """Return the children of `node` as a list. Subclasses must override
170 raise NotImplementedError()
172 class HashableDocutilsNodeImpl(HashableImpl
):
173 """Implements equality for a docutils `Node`."""
176 super(self
.__class
__, self
).__init
__(Node
)
178 if __name__
== '__main__':
179 hashableImpl
= HashableDocutilsNodeImpl()
180 hashableImpl
.debug
= True