Skip to content

Commit 343a0f2

Browse files
authored
Merge pull request #87 from petercorke/fix/graph-repr
fix: repr(g) is the one-line summary, show() lists the vertices
2 parents 4e1c1f9 + 548fb5f commit 343a0f2

2 files changed

Lines changed: 90 additions & 39 deletions

File tree

‎src/pgraph/PGraph.py‎

Lines changed: 42 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -323,15 +323,38 @@ def __str__(self) -> str:
323323
>>> g.add_edge(v2, v3)
324324
>>> str(g)
325325
326-
:seealso: :meth:`show`
326+
:seealso: :meth:`__repr__` :meth:`show`
327327
"""
328328
s = f"{self.__class__.__name__}: {self.n:,} {'vertex' if self.n==1 else 'vertices'}, {self.ne:,} edge{'s'[:self.ne^1]}, {self.nc:,} component{'s'[:self.nc^1]}"
329329
return s
330330

331331
def __repr__(self) -> str:
332-
# NOTE: this is shadowed by the __repr__ defined further below in
333-
# this class, which is the one actually used -- kept as-is here to
334-
# avoid changing behaviour as part of a typing-only pass.
332+
"""
333+
Representation of the graph, the same one-line summary as :meth:`__str__`
334+
335+
:return: one-line summary of vertex/edge/component counts
336+
:rtype: str
337+
338+
The representation of a graph does not list its vertices, since that
339+
would be as long as the graph, and a ``repr`` is what appears in a
340+
notebook cell, a debugger, a log or an error message. Use
341+
:meth:`show` to list them.
342+
343+
.. runblock:: pycon
344+
345+
>>> from pgraph import UGraph
346+
>>> g = UGraph()
347+
>>> v1 = g.add_vertex(coord=[0,0], name='v1')
348+
>>> v2 = g.add_vertex(coord=[1,1], name='v2')
349+
>>> g.add_edge(v1, v2)
350+
>>> repr(g)
351+
352+
.. versionchanged:: 1.1
353+
Was one line per vertex, with its name, coordinate and component;
354+
that is now printed by :meth:`show`.
355+
356+
:seealso: :meth:`__str__` :meth:`show`
357+
"""
335358
return str(self)
336359

337360
@classmethod
@@ -1501,7 +1524,11 @@ def remove(self, x: Edge | Vertex) -> None:
15011524

15021525
def show(self) -> None:
15031526
"""
1504-
Print a summary of all vertices and edges to stdout
1527+
Print a listing of all vertices and edges to stdout
1528+
1529+
Each vertex is shown with its name, coordinate and component number,
1530+
and each edge by its endpoints, cost and data. This is as long as the
1531+
graph, unlike :meth:`__str__` and :meth:`__repr__`, which are one line.
15051532
15061533
.. runblock:: pycon
15071534
@@ -1512,11 +1539,18 @@ def show(self) -> None:
15121539
>>> g.add_edge(v1, v2)
15131540
>>> g.show()
15141541
1515-
:seealso: :meth:`__str__`
1542+
.. versionchanged:: 1.1
1543+
Each vertex is shown with its coordinate and component number,
1544+
which were what ``repr(g)`` listed before it became one line.
1545+
1546+
:seealso: :meth:`__str__` :meth:`plot`
15161547
"""
15171548
print("vertices:")
15181549
for v in self._vertexlist:
1519-
print(" " + str(v))
1550+
line = f" {v.name} at {v.coord}"
1551+
if v.label is not None:
1552+
line += f" component={v.label}"
1553+
print(line)
15201554
print("edges:")
15211555
for e in self._edges:
15221556
print(" " + str(e))
@@ -1808,33 +1842,6 @@ def heuristic(self, heuristic: Callable[[NDArray], float] | str) -> None:
18081842
"""
18091843
self._heuristic = self._metricfunc(heuristic)
18101844

1811-
def __repr__(self) -> str: # type: ignore[no-redef]
1812-
"""
1813-
Detailed representation of the graph, one line per vertex
1814-
1815-
:return: one line per vertex showing its name, coordinate and component
1816-
:rtype: str
1817-
1818-
.. runblock:: pycon
1819-
1820-
>>> from pgraph import UGraph
1821-
>>> g = UGraph()
1822-
>>> v1 = g.add_vertex(coord=[0,0], name='v1')
1823-
>>> v2 = g.add_vertex(coord=[1,1], name='v2')
1824-
>>> v3 = g.add_vertex(coord=[2,2], name='v3')
1825-
>>> g.add_edge(v1, v2)
1826-
>>> g.add_edge(v2, v3)
1827-
>>> repr(g)
1828-
1829-
"""
1830-
s = [f"{self.__class__.__name__}:"]
1831-
for vertex in self:
1832-
ss = f" {vertex.name} at {vertex.coord}"
1833-
if vertex.label is not None:
1834-
ss += f" component={vertex.label}"
1835-
s.append(ss)
1836-
return "\n".join(s)
1837-
18381845
def __getitem__(self, i: int | str | Vertex) -> Vertex:
18391846
"""
18401847
Get vertex (base class method)
@@ -2528,7 +2535,7 @@ def plot(
25282535
... g.add_edge(a, b);
25292536
>>> g.plot(block=None)
25302537
2531-
:seealso: :meth:`highlight_path` :meth:`highlight_edge`
2538+
:seealso: :meth:`highlight_path` :meth:`highlight_edge` :meth:`show`
25322539
"""
25332540
import matplotlib.pyplot as plt
25342541
from matplotlib.patches import FancyArrowPatch

‎tests/test_graph.py‎

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11

2+
import contextlib
3+
import io
24
import unittest
35
import numpy as np
46
import numpy.testing as nt
@@ -45,11 +47,53 @@ def test_str(self):
4547
self.assertEqual(str(g), "DGraph: 2 vertices, 1 edge, 1 component")
4648

4749

50+
# the repr is the one-line summary, not a listing of the vertices
51+
self.assertEqual(repr(g), "DGraph: 2 vertices, 1 edge, 1 component")
52+
53+
def test_repr_is_one_line_however_big_the_graph(self):
54+
g = UGraph()
55+
for i in range(500):
56+
g.add_vertex([i, i], name=f"v{i}")
4857
s = repr(g)
49-
self.assertIsInstance(s, str)
50-
lines = s.split('\n')
51-
self.assertEqual(len(lines), 3) # class-name header + one line per vertex
52-
self.assertEqual(lines[0], "DGraph:")
58+
self.assertNotIn("\n", s)
59+
self.assertEqual(s, str(g))
60+
self.assertEqual(s, "UGraph: 500 vertices, 0 edges, 500 components")
61+
62+
def test_show_lists_vertices_and_edges(self):
63+
g = UGraph()
64+
a = g.add_vertex([1, 2], name="a")
65+
b = g.add_vertex([3, 4], name="b")
66+
g.add_vertex([5, 6], name="c")
67+
g.add_edge(a, b, cost=2.5)
68+
69+
out = io.StringIO()
70+
with contextlib.redirect_stdout(out):
71+
g.show()
72+
lines = out.getvalue().splitlines()
73+
74+
# a vertex line has the name, the coordinate and the component number,
75+
# which are what the repr used to list; c is a component of its own
76+
self.assertEqual(
77+
lines,
78+
[
79+
"vertices:",
80+
" a at [1 2] component=0",
81+
" b at [3 4] component=0",
82+
" c at [5 6] component=1",
83+
"edges:",
84+
" Edge{[a] -- [b], cost=2.5}",
85+
],
86+
)
87+
88+
def test_show_a_vertex_without_a_coordinate(self):
89+
g = UGraph()
90+
g.add_vertex(name="x")
91+
92+
out = io.StringIO()
93+
with contextlib.redirect_stdout(out):
94+
g.show()
95+
96+
self.assertIn(" x at None", out.getvalue())
5397

5498

5599
def test_str_has_thousands_separators(self):

0 commit comments

Comments
 (0)