Make XModuleDescriptor hashable again (#22501)

This commit is contained in:
Jeremy Bowman
2019-12-11 14:37:07 -05:00
committed by GitHub
parent d0ab1fc423
commit ca1265a679
2 changed files with 20 additions and 12 deletions

View File

@@ -1263,6 +1263,17 @@ class XModuleDescriptor(XModuleDescriptorToXBlockMixin, HTMLSnippet, ResourceTem
all(getattr(self, field.name) == getattr(other, field.name)
for field in self.fields.values()))
def __hash__(self): # pylint: disable=useless-super-delegation
"""
This isn't technically appropriate since descriptors are actually mutable,
but in practice we rarely modify them after creation or instantiate two
equivalent descriptors in the same process. And we perform graph
operations on large collections of XBlocks that have simply unacceptable
performance if we have to rely on lists and equality rather than sets,
dictionaries, and identity-based hash functions.
"""
return super(XModuleDescriptor, self).__hash__()
def __repr__(self):
return (
"{0.__class__.__name__}("

View File

@@ -127,7 +127,7 @@ def traverse_topologically(
used to limit which nodes are actually yielded.
Arguments:
start_node (any type) - The starting node for the
start_node (any hashable type) - The starting node for the
traversal.
get_parents (node->[node]) - Function that returns a list of
@@ -204,7 +204,7 @@ def traverse_post_order(start_node, get_children, filter_func=None):
stack = deque([_Node(start_node, get_children)])
# Keep track of which nodes have been visited.
visited = []
visited = set()
while stack:
# Peek at the current node at the top of the stack.
@@ -226,7 +226,7 @@ def traverse_post_order(start_node, get_children, filter_func=None):
# Since there are no children left, visit the node and
# remove it from the stack.
yield current.node
visited.append(current.node)
visited.add(current.node)
stack.pop()
else:
@@ -264,8 +264,7 @@ def _traverse_generic(
# Keep track of which nodes have been visited and whether they
# were in fact yielded.
visited = [] # nodes
yield_results = [] # booleans
yield_results = {} # dict(node:boolean)
# While there are more nodes on the stack...
while stack:
@@ -281,17 +280,16 @@ def _traverse_generic(
parents = get_parents(current_node)
# If all of the parents have not yet been visited, continue.
if not all(parent in visited for parent in parents):
if not all(parent in yield_results for parent in parents):
continue
# If none of the parents have yielded, continue, unless
# specified otherwise (via yield_descendants_of_unyielded).
elif not yield_descendants_of_unyielded and not any(
yield_results[visited.index(parent)] for parent in parents):
elif not yield_descendants_of_unyielded and not any(yield_results[parent] for parent in parents):
continue
# If the current node has already been visited, continue.
if current_node not in visited:
if current_node not in yield_results:
# For a topological sort, it's important that we visit
# the children even if the parent isn't yielded, in case
@@ -318,7 +316,7 @@ def _traverse_generic(
unvisited_children = list(
child
for child in get_children(current_node)
if child not in visited
if child not in yield_results
)
# Add the node's unvisited children to the stack in reverse
@@ -334,5 +332,4 @@ def _traverse_generic(
# Keep track of whether or not the node was yielded so we
# know whether or not to yield its children.
visited.append(current_node)
yield_results.append(should_yield_node)
yield_results[current_node] = should_yield_node