How to print an object in Python
Discover multiple ways to print a Python object. We cover tips, real-world applications, and debugging common printing errors.

For any Python developer, it's essential to print objects to debug and understand code behavior. While print() is common, custom objects need special methods for a clear, readable output.
In this article, you'll discover techniques to customize object output with methods like __str__ and __repr__. You'll also find practical tips, real-world applications, and debugging advice to master object representation.
Basic printing with print()
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
person = Person("Alice", 30)
print(person)--OUTPUT--<__main__.Person object at 0x7f1234567890>
When you use the print() function on the person object, you get Python's default object representation. This output isn't a bug; it's showing you the object's class (Person) and its unique memory address. While this confirms the object exists, it doesn't reveal its attributes like name or age.
This happens because the custom Person class lacks instructions on how to be displayed as a readable string. Without a specific method to define its string output, Python defaults to this generic format. Understanding the fundamentals of creating a class in Python is essential before customizing object representations. The next step is to provide that instruction so printing the object gives you useful information.
Basic printing techniques
To provide those instructions and get more useful output, you can start with a few fundamental techniques for formatting and representing your objects.
Using f-strings to format object attributes
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
person = Person("Bob", 25)
print(f"Person: {person.name}, {person.age} years old")--OUTPUT--Person: Bob, 25 years old
F-strings provide a quick and direct way to create a readable string from your object's attributes. By prefixing the string with an f, you can embed expressions like person.name and person.age directly inside curly braces. For more advanced formatting techniques, see our guide on using f-strings in Python. This gives you precise control over the output for any single print call.
- It's explicit: You decide exactly what to show and how to format it each time.
- It's repetitive: This approach isn't reusable. You have to write the same formatting logic every time you want to print the object's details.
Using the str() and repr() functions
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
person = Person("Charlie", 35)
print(str(person)) # Same as print(person)
print(repr(person)) # More detailed representation--OUTPUT--<__main__.Person object at 0x7f1234567890>
<__main__.Person object at 0x7f1234567890>
The str() and repr() functions are Python's built-in tools for getting string representations of an object. As you can see, without any custom logic in the Person class, both functions produce the same default output. This happens because the print() function itself calls str() behind the scenes.
str()aims to provide a readable, user-friendly output.repr()is meant to return an unambiguous string that, ideally, could be used to recreate the object.
Customizing object output with __str__ and __repr__
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __str__(self):
return f"{self.name} ({self.age})"
def __repr__(self):
return f"Person(name='{self.name}', age={self.age})"
person = Person("David", 40)
print(person) # Uses __str__
print(repr(person)) # Uses __repr__--OUTPUT--David (40)
Person(name='David', age=40)
By implementing the special __str__ and __repr__ methods, you gain full control over your object's string representation. When you print() an object, Python automatically calls its __str__ method for a user-friendly display. The __repr__ method, however, is meant for developers.
__str__should return a clean, readable string for end-users.__repr__should return an unambiguous string that's useful for debugging—ideally, it looks like a valid Python expression that could recreate the object.
Advanced printing techniques
While __str__ and __repr__ are powerful, Python offers even more advanced tools for inspecting complex objects and creating visually rich, structured output.
Pretty printing with pprint
from pprint import pprint
class Person:
def __init__(self, name, age, hobbies):
self.name = name
self.age = age
self.hobbies = hobbies
person = Person("Eve", 28, ["reading", "swimming", "coding"])
pprint(vars(person), width=40)--OUTPUT--{'age': 28,
'hobbies': ['reading',
'swimming',
'coding'],
'name': 'Eve'}
When objects contain nested data like lists or dictionaries, a standard `print()` can produce messy output. Python's `pprint` module is built for this exact scenario. It formats complex data structures so they are easy to read and memory-efficient. In this example, `vars(person)` first converts the object's attributes into a dictionary, which `pprint` then organizes neatly.
- It's especially useful for debugging objects with many attributes or nested data.
- The output is automatically sorted and indented for clarity.
- You can customize the layout with arguments like `width`.
Inspecting objects with vars() and dir()
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
person = Person("Frank", 32)
print(vars(person)) # Dictionary of attributes
print([attr for attr in dir(person) if not attr.startswith('_')])--OUTPUT--{'name': 'Frank', 'age': 32}
['age', 'name']
When you need to peek inside an object without a predefined print format, the built-in functions vars() and dir() are perfect for dynamic inspection. Since vars() returns object attributes as a dictionary, understanding creating dictionaries in Python helps you work with this output effectively.
vars(person)returns a dictionary of the object's attributes and their current values. It’s a straightforward way to see the object's state at a glance.dir(person)is more comprehensive, listing all attributes including methods and special dunder methods. The example filters this list to show only the public attributes you'd typically work with.
Enhanced visualization with the rich library
from rich import print as rprint
from rich.pretty import pprint as rpprint
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
person = Person("Grace", 29)
rprint(f"[bold blue]{person.__class__.__name__}[/bold blue]: {vars(person)}")
rpprint(person, expand_all=True)--OUTPUT--Person: {'name': 'Grace', 'age': 29}
<__main__.Person object at 0x7f1234567890>
name = 'Grace'
age = 29
For truly advanced terminal output, the rich library adds color and sophisticated formatting. It goes beyond standard printing by turning your terminal into a richer display for debugging and presentation.
- The
rprintfunction is a powerful replacement forprint()that understands special markup, like[bold blue], to style your text. - Meanwhile,
rpprintautomatically inspects and formats objects, displaying their attributes with syntax highlighting and clean indentation—making complex data much easier to read.
Move faster with Replit
Replit is an AI-powered development platform that comes with all Python dependencies pre-installed, so you can skip setup and start coding instantly.
Instead of piecing together techniques, you can use Agent 4 to build complete apps from a description. It takes your idea to a working product by handling the code, databases, APIs, and deployment. You can move from learning concepts to building real tools, such as:
- A utility that converts a list of user objects into a formatted CSV string for export.
- A custom log formatter that takes various data types and produces a standardized, readable output for easier debugging.
- A dynamic content generator that creates formatted strings from product objects to display in a web interface.
Simply describe your app, and Replit will write the code, test it, and fix issues automatically, all within your browser.
Common errors and challenges
Even with the right methods, you might encounter tricky issues like circular references, float precision errors, or problems with dynamic objects.
Handling circular references in __str__ methods
A circular reference happens when two objects refer to each other, creating a loop. If your __str__ method prints an object that refers back to the first one, it triggers an infinite recursion that Python halts with a RecursionError. The following code demonstrates this exact issue.
class Department:
def __init__(self, name, manager=None):
self.name = name
self.manager = manager
def __str__(self):
return f"Department: {self.name}, Manager: {self.manager}"
class Employee:
def __init__(self, name, department=None):
self.name = name
self.department = department
def __str__(self):
return f"Employee: {self.name}, Dept: {self.department}"
dept = Department("Engineering")
employee = Employee("Alice", dept)
dept.manager = employee
print(dept) # RecursionError: maximum recursion depth exceeded
Printing the dept object triggers its __str__ method, which then calls the employee's __str__. This, in turn, calls the dept's __str__ again, creating an endless loop. The following code demonstrates one way to break this cycle.
class Department:
def __init__(self, name, manager=None):
self.name = name
self.manager = manager
def __str__(self):
manager_name = self.manager.name if self.manager else "None"
return f"Department: {self.name}, Manager: {manager_name}"
class Employee:
def __init__(self, name, department=None):
self.name = name
self.department = department
def __str__(self):
dept_name = self.department.name if self.department else "None"
return f"Employee: {self.name}, Dept: {dept_name}"
dept = Department("Engineering")
employee = Employee("Alice", dept)
dept.manager = employee
print(dept) # Department: Engineering, Manager: Alice
The solution breaks the infinite loop by avoiding a nested call to another object's __str__ method. Instead of printing the entire self.manager object, the Department class now specifically prints the self.manager.name attribute. This simple change prevents the recursion.
You should watch for this issue whenever you're working with objects that have two-way relationships, such as parent-child links in a database model or nodes in a doubly linked list.
Fixing float precision display in printed output
Floating-point numbers often produce messy, overly long outputs due to how computers store them. This can make financial calculations or scientific data look unprofessional and hard to read. You need to control the precision for clean, predictable string representations.
The following code demonstrates this issue, where simple division and a list of prices result in inconsistent and unwieldy decimal places.
# Printing calculation results without controlling precision
result = 1/3
print(f"The result is {result}")
prices = [10.1, 15.59999, 25.0]
for price in prices:
print(f"Price: ${price}")
The output shows raw float values because Python doesn't automatically round them, leading to long, messy decimals. The next example shows how to format these numbers for a clean, consistent display.
# Controlling decimal precision in printed output
result = 1/3
print(f"The result is {result:.2f}")
prices = [10.1, 15.59999, 25.0]
for price in prices:
print(f"Price: ${price:.2f}")
The fix is to use a format specifier inside the f-string. By adding :.2f to your variable, like {price:.2f}, you tell Python to format the number as a float with exactly two decimal places. This rounds the number cleanly, giving you consistent and readable output. It's a simple but powerful way to control how numbers appear, which is crucial for financial data, scientific measurements, or any time you need polished numerical displays.
Debugging print statements with dynamic objects
When you're debugging, print statements can sometimes mislead you, especially with mutable objects like lists in AI coding. A function might change an object's state without you realizing it, making your print outputs confusing and sending you chasing down the wrong bugs.
The following code shows how a function, analyze_data, modifies a list that was passed to it. This causes the original data to change unexpectedly, so the final print statement doesn't show the list's original state.
def analyze_data(data):
print(f"Analyzing data: {data}")
data.append('new_value')
print(f"Analysis complete")
return data
my_data = ['value1', 'value2']
result = analyze_data(my_data)
print(f"Original data should be unchanged: {my_data}")
The analyze_data function alters the original my_data list because Python passes mutable objects like lists by reference. This causes the final print statement to show the modified list, not its original state. The code below shows how to fix this.
def analyze_data(data):
print(f"Analyzing data: {data.copy()}")
working_copy = data.copy()
working_copy.append('new_value')
print(f"Analysis complete with result: {working_copy}")
return working_copy
my_data = ['value1', 'value2']
result = analyze_data(my_data)
print(f"Original data is unchanged: {my_data}")
The fix is to work on a shallow copy of the list. By calling data.copy(), the function creates a new list instead of modifying the original one. This ensures your original my_data remains unchanged, preventing unexpected side effects. For more details on different copying methods and when to use each, see our guide on copying lists in Python. It's a crucial safeguard whenever you pass mutable objects—like lists or dictionaries—into functions and need to preserve their original state. This prevents your print statements from showing misleading results during debugging.
Real-world applications
With a handle on common printing errors, you can apply these skills to practical tasks like creating detailed logs and data reports.
Logging objects with custom __repr__ for debugging
For effective debugging, a custom __repr__ method provides a clear and unambiguous object representation that makes your application logs much more informative.
import logging
class Order:
def __init__(self, id, items, total):
self.id = id
self.items = items
self.total = total
def __repr__(self):
return f"Order(id={self.id}, items={len(self.items)}, total=${self.total})"
logging.basicConfig(level=logging.DEBUG)
order = Order("A12345", ["Laptop", "Mouse", "Keyboard"], 1249.99)
logging.debug(f"Processing {order}")
This example shows how Python's logging module works with custom objects. When the order object is used inside the f-string for logging.debug(), Python automatically calls its __repr__ method to get a string.
- The custom
__repr__method in theOrderclass returns a structured summary. - It formats key details like the order
id, the count ofitems, and thetotalcost.
As a result, the log entry contains a precise snapshot of the object's state, not a generic memory address. For persistent debugging, you can combine this with creating log files in Python to save these detailed object representations to disk.
Generating formatted data reports with object printing
You can also pull data directly from a collection of objects, using loops and formatted strings to build clean, structured reports for analysis.
def generate_report(data_objects):
print(f"PERFORMANCE REPORT ({len(data_objects)} items)")
print("-" * 45)
for i, obj in enumerate(data_objects, 1):
avg = sum(obj.values) / len(obj.values)
print(f"Item #{i}: {obj.name:<15} | Avg: {avg:.2f} | Values: {obj.values}")
overall_avg = sum(sum(o.values) for o in data_objects) / sum(len(o.values) for o in data_objects)
print("-" * 45)
print(f"Overall Average: {overall_avg:.2f}")
class DataSeries:
def __init__(self, name, values):
self.name = name
self.values = values
data = [
DataSeries("CPU Usage", [12.5, 45.2, 38.7, 29.3]),
DataSeries("Memory", [64.2, 72.1, 68.0, 81.5]),
DataSeries("Disk I/O", [5.2, 19.8, 12.3, 8.7])
]
generate_report(data)
The generate_report function takes a list of custom DataSeries objects and transforms them into a formatted summary. It iterates through the list, calculating and printing key metrics for each item.
- It uses f-string format specifiers like
{obj.name:<15}to left-align text and{avg:.2f}to round numbers to two decimal places, ensuring the output is clean and aligned. - The function calculates an average for each series and then computes an overall average for the entire dataset.
This demonstrates how you can programmatically create readable reports directly from your object data through vibe coding.
Get started with Replit
Now, turn these concepts into a real tool. Describe what you want to build to Replit Agent, like "create a custom logger for user sessions" or "generate a formatted inventory report from product objects".
Replit Agent writes the code, tests for errors, and deploys your app from a simple description. Start building with Replit.
Describe what you want to build, and Replit Agent writes the code, handles the infrastructure, and ships it live. Go from idea to real product, all in your browser.
Describe what you want to build, and Replit Agent writes the code, handles the infrastructure, and ships it live. Go from idea to real product, all in your browser.



