Generally, if a function can't handle an exception, let the exception pass up the call stack in case a higher level can handle it. For example, don't assert that node is in the graph. Assume it is and try the operation. If the node isn't in the graph, an IndexError will be thrown. (Some people say "it's easier to ask forgiveness than get permission".)
Presuming that friendships is a Graph, and that person1 and person2 are nodes in the Graph, common() can be implemented using the neighbors() method and set operations:
def common(friendships, person1, person2):
"""Return the number of common friends of person1 and person2."""
return friendshipfriendships.neighbors(person1) & friendshipfriendships.neighbors(person2)
A neighbors() and nodes() can be simplified:
def neighbors(self, node):
"""Return a copy of the set of neighbors of node.
Assume the graph has the node.
"""
return set(self.graph[node])
def nodes(self):
"""Return a set of all nodes in the graph."""
return set(self.graph)
The doc-string suggests Graph is a non-directed graph, so edges() can be simplified:
def edges(self):
"""Return the set of all edges in the graph.
An edge is a tuple (node1, node2).
Only one of (node1, node2) and (node2, node1) is included in the set.
"""
seen = set()
result = set()
for node1, neighbors in self.graph.items():
result.union((node1, node2) for node2 in neighbors if node2 not in seen)
seen.add(node1)
return result