How to Pretty Print a Linked List in Python
When building custom data structures in Python, the default output from print() is often unhelpful: typically showing something like <__main__.LinkedList object at 0x7f...>. By leveraging Python's dunder methods (double underscore methods), you can make your linked list display beautifully with the standard print() function, just like built-in types.
In this guide, you will learn how to use Python's __str__ and __repr__ methods to pretty print linked lists in a clean, readable format.
What Are Dunder Methods?
Dunder methods (short for "double under" methods) are special methods in Python that start and end with double underscores, like __init__, __str__, and __repr__. They allow you to define how your custom objects behave with built-in Python operations.
The two most relevant dunder methods for pretty printing are:
| Method | Called By | Purpose |
|---|---|---|
__str__ | print(), str() | Human-readable representation |
__repr__ | Interactive console, repr() | Unambiguous developer representation |
When you call print(my_object), Python internally calls my_object.__str__() and prints the returned string.
Basic Pretty Print: List-Style Output
Here is a complete linked list implementation with a __str__ method that formats the output like a Python list:
class Node:
def __init__(self, val=None):
self.val = val
self.next = None
class LinkedList:
def __init__(self):
self.head = None
def append(self, val):
"""Add a node to the end of the list."""
new_node = Node(val)
if self.head is None:
self.head = new_node
return
current = self.head
while current.next:
current = current.next
current.next = new_node
def __str__(self):
"""Return a list-style string representation."""
elements = []
current = self.head
while current:
elements.append(str(current.val))
current = current.next
return "[" + ", ".join(elements) + "]"
# Usage
ll = LinkedList()
ll.append(10)
ll.append(15)
ll.append(20)
print(ll)
Output:
[10, 15, 20]
The print() function automatically calls __str__(), which traverses the linked list, collects all values, and returns a formatted string.
Arrow-Style Output
For a more visual representation that emphasizes the linked structure, use arrows between nodes:
class Node:
def __init__(self, val=None):
self.val = val
self.next = None
class LinkedList:
def __init__(self):
self.head = None
def append(self, val):
new_node = Node(val)
if self.head is None:
self.head = new_node
return
current = self.head
while current.next:
current = current.next
current.next = new_node
def __str__(self):
"""Return an arrow-style string representation."""
elements = []
current = self.head
while current:
elements.append(str(current.val))
current = current.next
if elements:
return " -> ".join(elements) + " -> None"
return "Empty LinkedList"
ll = LinkedList()
ll.append(10)
ll.append(15)
ll.append(20)
print(ll)
Output:
10 -> 15 -> 20 -> None
This format clearly shows the chain of nodes and the None terminator at the end.
Adding __repr__ for Developer Context
While __str__ provides a user-friendly display, __repr__ gives a more detailed, developer-focused representation. It is shown in the interactive console and when objects are inside containers:
class Node:
def __init__(self, val=None):
self.val = val
self.next = None
class LinkedList:
def __init__(self):
self.head = None
def append(self, val):
new_node = Node(val)
if self.head is None:
self.head = new_node
return
current = self.head
while current.next:
current = current.next
current.next = new_node
def __len__(self):
"""Return the number of nodes."""
count = 0
current = self.head
while current:
count += 1
current = current.next
return count
def __str__(self):
elements = []
current = self.head
while current:
elements.append(str(current.val))
current = current.next
return "[" + ", ".join(elements) + "]"
def __repr__(self):
return f"LinkedList(length={len(self)}, values={self.__str__()})"
ll = LinkedList()
ll.append(10)
ll.append(15)
ll.append(20)
# __str__ is called by print()
print(ll)
# __repr__ is shown in the console and containers
print(repr(ll))
print([ll]) # When inside a list, __repr__ is used
Output:
[10, 15, 20]
LinkedList(length=3, values=[10, 15, 20])
[LinkedList(length=3, values=[10, 15, 20])]
__str__ vs. __repr____str__: Called byprint()andstr(). Should be human-readable.__repr__: Called by the interactive console,repr(), and when the object is inside a container (like a list). Should be unambiguous and ideally show enough information to recreate the object.
If only one is defined, define __repr__. Python falls back to __repr__ when __str__ is not available, but not vice versa.
Pretty Printing an Empty List
Always handle the edge case of an empty linked list:
ll = LinkedList()
print(ll)
Output:
[]
The __str__ method returns "[]" when the list has no nodes, matching the behavior of an empty Python list.
Complete Implementation With Multiple Display Formats
Here is a full implementation that supports multiple output styles:
class Node:
def __init__(self, val=None):
self.val = val
self.next = None
class LinkedList:
def __init__(self):
self.head = None
def append(self, val):
new_node = Node(val)
if self.head is None:
self.head = new_node
return
current = self.head
while current.next:
current = current.next
current.next = new_node
def __len__(self):
count = 0
current = self.head
while current:
count += 1
current = current.next
return count
def __str__(self):
"""List-style: [10, 15, 20]"""
elements = []
current = self.head
while current:
elements.append(repr(current.val))
current = current.next
return "[" + ", ".join(elements) + "]"
def __repr__(self):
return f"LinkedList({self.__str__()})"
def to_arrow_string(self):
"""Arrow-style: 10 -> 15 -> 20 -> None"""
elements = []
current = self.head
while current:
elements.append(str(current.val))
current = current.next
if elements:
return " -> ".join(elements) + " -> None"
return "Empty"
if __name__ == "__main__":
ll = LinkedList()
ll.append(10)
ll.append(15)
ll.append(20)
# Default print uses __str__
print(f"List style: {ll}")
print(f"Arrow style: {ll.to_arrow_string()}")
print(f"Repr: {repr(ll)}")
print(f"Length: {len(ll)}")
# Empty list
empty = LinkedList()
print(f"\nEmpty list: {empty}")
print(f"Empty arrow: {empty.to_arrow_string()}")
Output:
List style: [10, 15, 20]
Arrow style: 10 -> 15 -> 20 -> None
Repr: LinkedList([10, 15, 20])
Length: 3
Empty list: []
Empty arrow: Empty
Pretty Printing the Node Class Too
You can also add __str__ to the Node class for individual node display:
class Node:
def __init__(self, val=None):
self.val = val
self.next = None
def __str__(self):
return f"Node({self.val})"
def __repr__(self):
return f"Node({self.val!r})"
node = Node(42)
print(node) # Node(42)
print(repr(node)) # Node(42)
Output:
Node(42)
Node(42)
Why This Matters
Without __str__, printing a linked list produces useless output:
# ❌ Without __str__
class BasicLinkedList:
def __init__(self):
self.head = None
ll = BasicLinkedList()
print(ll)
# <__main__.BasicLinkedList object at 0x7f8b8c0a1d90>
With __str__, you get clean, informative output:
# ✅ With __str__
print(ll)
# [10, 15, 20]
This makes debugging, logging, and interactive development significantly easier.
Conclusion
Pretty printing a linked list in Python is accomplished by implementing the __str__ dunder method in your linked list class.
When print() is called on an instance, Python automatically invokes __str__(), which traverses the list, collects node values, and returns a formatted string.
Add __repr__ for developer-facing output, and consider providing multiple display formats (list-style, arrow-style) for different contexts.
These dunder methods are a powerful Pythonic pattern that makes custom data structures behave naturally with built-in functions: no separate print utilities needed.