7272 # Python 3.x compatibility
7373 iteritems = dict .items
7474
75+ try :
76+ zip_longest = itertools .izip_longest
77+ except AttributeError : # pragma: PY3
78+ # Python 3.x compatibility
79+ zip_longest = itertools .zip_longest
80+
7581IS_INTERACTIVE = False
7682try : # pragma: nocover
7783 import graphviz
@@ -1000,7 +1006,10 @@ def _show_graph(objs, edge_func, swap_source_target,
10001006 continue
10011007 if cull_func is not None and cull_func (target ):
10021008 continue
1003- neighbours = edge_func (target )
1009+ edges = edge_func (target )
1010+ counts = collections .Counter (id (v ) for v in edges )
1011+ neighbours = list ({id (v ): v for v in edges }.values ())
1012+ del edges
10041013 ignore .add (id (neighbours ))
10051014 n = 0
10061015 skipped = 0
@@ -1016,9 +1025,13 @@ def _show_graph(objs, edge_func, swap_source_target,
10161025 srcnode , tgtnode = target , source
10171026 else :
10181027 srcnode , tgtnode = source , target
1019- elabel = _edge_label (srcnode , tgtnode , shortnames )
1020- f .write (' %s -> %s%s;\n ' % (_obj_node_id (srcnode ),
1021- _obj_node_id (tgtnode ), elabel ))
1028+ for elabel , _ in zip_longest (
1029+ _edge_labels (srcnode , tgtnode , shortnames ),
1030+ range (counts [id (source )]),
1031+ fillvalue = '' ,
1032+ ):
1033+ f .write (' %s -> %s%s;\n ' % (_obj_node_id (srcnode ),
1034+ _obj_node_id (tgtnode ), elabel ))
10221035 if id (source ) not in depth :
10231036 depth [id (source )] = tdepth + 1
10241037 queue .append (source )
@@ -1208,43 +1221,51 @@ def _gradient(start_color, end_color, depth, max_depth):
12081221 return h , s , v
12091222
12101223
1211- def _edge_label (source , target , shortnames = True ):
1224+ def _edge_labels (source , target , shortnames = True ):
12121225 if (_isinstance (target , dict )
12131226 and target is getattr (source , '__dict__' , None )):
1214- return ' [label="__dict__",weight=10]'
1227+ return [ ' [label="__dict__",weight=10]' ]
12151228 if _isinstance (source , types .FrameType ):
12161229 if target is source .f_locals :
1217- return ' [label="f_locals",weight=10]'
1230+ return [ ' [label="f_locals",weight=10]' ]
12181231 if target is source .f_globals :
1219- return ' [label="f_globals",weight=10]'
1232+ return [ ' [label="f_globals",weight=10]' ]
12201233 if _isinstance (source , types .MethodType ):
12211234 try :
12221235 if target is source .__self__ :
1223- return ' [label="__self__",weight=10]'
1236+ return [ ' [label="__self__",weight=10]' ]
12241237 if target is source .__func__ :
1225- return ' [label="__func__",weight=10]'
1238+ return [ ' [label="__func__",weight=10]' ]
12261239 except AttributeError : # pragma: nocover
12271240 # Python < 2.6 compatibility
12281241 if target is source .im_self :
1229- return ' [label="im_self",weight=10]'
1242+ return [ ' [label="im_self",weight=10]' ]
12301243 if target is source .im_func :
1231- return ' [label="im_func",weight=10]'
1244+ return [ ' [label="im_func",weight=10]' ]
12321245 if _isinstance (source , types .FunctionType ):
1233- for k in dir (source ):
1234- if target is getattr (source , k ):
1235- return ' [label="%s",weight=10]' % _quote (k )
1246+ return [
1247+ ' [label="%s",weight=10]' % _quote (k )
1248+ for k in dir (source )
1249+ if target is getattr (source , k )
1250+ ]
12361251 if _isinstance (source , dict ):
1237- for k , v in iteritems (source ):
1238- if v is target :
1239- if _isinstance (k , basestring ) and _is_identifier (k ):
1240- return ' [label="%s",weight=2]' % _quote (k )
1241- else :
1242- if shortnames :
1243- tn = _short_typename (k )
1244- else :
1245- tn = _long_typename (k )
1246- return ' [label="%s"]' % _quote (tn + "\n " + _safe_repr (k ))
1247- return ''
1252+ tn = _short_typename if shortnames else _long_typename
1253+ return [
1254+ (
1255+ ' [label="%s",weight=2]' % _quote (k )
1256+ if _isinstance (k , basestring ) and _is_identifier (k )
1257+ else (
1258+ ' [label="%s"]' % _quote (tn (k ) + "\n " + _safe_repr (k ))
1259+ )
1260+ )
1261+ for k , v in iteritems (source )
1262+ if v is target
1263+ ]
1264+ return []
1265+
1266+
1267+ def _edge_label (* args , ** kwargs ):
1268+ return next (iter (_edge_labels (* args , ** kwargs )), '' )
12481269
12491270
12501271_is_identifier = re .compile ('[a-zA-Z_][a-zA-Z_0-9]*$' ).match
0 commit comments