How to see all attributes of an object in Python
Learn how to see all attributes of a Python object. Discover different methods, tips, real-world applications, and how to debug common errors.

In Python, you often need to inspect objects to understand their structure and available attributes. This is a key skill for effective debugging and dynamic code manipulation.
Here, you'll explore techniques like the dir() function and the __dict__ attribute. You'll also find practical tips, real-world applications, and debugging advice to help you master object inspection.
Using the dir() function
my_string = "Hello, world!"
attributes = dir(my_string)
print(attributes[:5]) # Just showing first 5 for brevity--OUTPUT--['__add__', '__class__', '__contains__', '__delattr__', '__dir__']
The dir() function is your go-to for discovering an object's capabilities. When called on the my_string object, it returns a list of all the names in its namespace. This includes methods, attributes, and the special "dunder" methods that define its core behavior.
The output shows the first few of these dunder methods. For instance, __add__ enables string concatenation with the + operator, while __contains__ allows you to use the in keyword for substring checks. It's a quick way to see what an object can do under the hood.
Basic attribute inspection methods
While dir() gives you a map of an object's namespace, you can also directly access its underlying attribute dictionary for more focused inspection and manipulation.
Using the vars() function
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
person = Person("Alice", 30)
print(vars(person))--OUTPUT--{'name': 'Alice', 'age': 30}
The vars() function returns an object's __dict__ attribute, which is a dictionary containing its writable attributes. It’s a clean way to see the object's current state at a glance.
- Unlike
dir(), which lists all attributes,vars()focuses only on the instance's data. - The output for
vars(person)is{'name': 'Alice', 'age': 30}, directly reflecting the attributes you set in the__init__method.
This makes it incredibly useful for debugging or when you need to work with an object's data as a dictionary.
Accessing an object's __dict__ attribute
class Product:
def __init__(self, name, price):
self.name = name
self.price = price
product = Product("Laptop", 999.99)
print(product.__dict__)--OUTPUT--{'name': 'Laptop', 'price': 999.99}
Directly accessing an object's __dict__ attribute is another way to get at its core data. This special attribute holds a dictionary of the object's writable attributes, just as the Product example shows. It gives you a direct look into the instance's state.
- It provides the same underlying dictionary that the
vars()function retrieves. - This dictionary is mutable, allowing you to dynamically add, change, or remove an object's attributes after it's been created.
Using getattr() to access attributes dynamically
class Car:
def __init__(self, model, year):
self.model = model
self.year = year
car = Car("Tesla", 2023)
attributes = ["model", "year", "color"]
for attr in attributes:
print(f"{attr}: {getattr(car, attr, 'Not found')}")--OUTPUT--model: Tesla
year: 2023
color: Not found
The getattr() function lets you access an object's attributes using a string name. This is incredibly useful when the attribute you need to retrieve is determined dynamically at runtime, such as when iterating through a list of names.
- It takes three arguments: the object, the attribute name as a string, and an optional default value.
- This default value is a safety net. If an attribute doesn't exist—like
"color"in the example—getattr()returns the default instead of raising anAttributeError.
Advanced attribute exploration techniques
To go deeper than what dir() and vars() offer, you can turn to advanced techniques for filtering attributes and recursively exploring complex, nested objects.
Exploring attributes with the inspect module
import inspect
class Sample:
class_var = "I'm a class variable"
def __init__(self):
self.instance_var = "I'm an instance variable"
def method(self):
pass
print(inspect.getmembers(Sample, inspect.isfunction))--OUTPUT--[('method', <function Sample.method at 0x7f8a6b3a9160>)]
The inspect module is your toolkit for deep object analysis. Its getmembers() function returns a list of all members in an object, but its real strength lies in filtering. You can pass a predicate—a function that returns true or false—to narrow down the results.
- In this case,
inspect.isfunctionacts as the filter, tellinggetmembers()to find only members of theSampleclass that are functions. - This is why it isolates the
method, giving you a precise way to find specific types of attributes without the noise of everything else.
Filtering object attributes by name pattern
class DataAnalyzer:
def __init__(self):
self._private_data = [1, 2, 3]
self.public_results = []
def analyze(self):
pass
obj = DataAnalyzer()
public_attrs = [attr for attr in dir(obj) if not attr.startswith('_')]
print(public_attrs)--OUTPUT--['analyze', 'public_results']
You can also filter attributes by name, which is perfect for separating public from private members. A common Python convention is to prefix internal-use attributes with a single underscore, like _private_data. This code uses a list comprehension to build a new list from the output of dir().
- It checks each attribute name using
startswith('_'). - By including
if not, it effectively ignores all dunder methods and private attributes.
The result is a focused list containing only the public members: analyze and public_results.
Recursively exploring attributes of nested objects
def explore_attrs(obj, max_depth=1, current_depth=0):
if current_depth > max_depth or not hasattr(obj, '__dict__'):
return
for attr_name, attr_value in vars(obj).items():
print(f"{' ' * current_depth}{attr_name}: {type(attr_value).__name__}")
explore_attrs(attr_value, max_depth, current_depth + 1)
class Department:
def __init__(self, name):
self.name = name
class Employee:
def __init__(self, name, dept):
self.name = name
self.department = dept
emp = Employee("John", Department("Engineering"))
explore_attrs(emp, max_depth=2)--OUTPUT--name: str
department: Department
name: str
When objects contain other objects, you need a way to inspect the entire structure. The custom explore_attrs function handles this by recursively traversing nested attributes. It dives into each object's attributes using vars(), prints its contents, and then calls itself on the attribute values to explore the next level down.
- The
max_depthparameter is a crucial safeguard. It prevents the function from getting stuck in an infinite loop and keeps the output from becoming overwhelming. - In the example, it first inspects the
Employeeobject, then descends into the nestedDepartmentobject, revealing its attributes too.
Move faster with Replit
Replit is an AI-powered development platform that transforms natural language into working applications. Describe what you want to build, and Replit Agent creates it—complete with databases, APIs, and deployment.
For the object inspection techniques we've explored, Replit Agent can turn them into production-ready tools:
- Build a dynamic API documentation generator that inspects Python classes to list all public methods and attributes.
- Create a configuration validator that uses
getattr()to ensure settings objects have all required properties before deployment. - Deploy an interactive object explorer that visualizes nested data structures, much like the recursive
explore_attrsfunction.
Describe your app idea, and Replit Agent writes the code, tests it, and fixes issues automatically, all within your browser.
Common errors and challenges
While inspecting objects is powerful, you'll encounter common pitfalls like missing attributes, type errors, and infinite loops.
Trying to access an attribute that doesn't exist will raise an AttributeError and halt your program. This is a frequent issue when attribute names are determined dynamically or come from unpredictable data sources. The getattr() function is the perfect solution. By providing a default value as its third argument, you can gracefully handle these cases.
- Instead of crashing, your code will simply return the default value you specified.
- This makes your application more robust and prevents unexpected failures when dealing with incomplete or varied object structures.
If you call vars() on an object that doesn't have a __dict__ attribute, such as a string or an integer, Python will raise a TypeError. This is because vars() is designed to work with instances of classes you've defined, not most built-in types. Built-in types don't manage their attributes through a mutable dictionary.
- A simple check like
hasattr(obj, '__dict__')will tell you if it's safe to usevars(obj). - This is a key defensive practice when writing code that needs to inspect objects of unknown types.
When objects refer to each other—a situation known as a circular reference—a recursive inspection function can get trapped in an infinite loop. For example, an Employee object might reference its Manager, while the Manager object's list of subordinates points back to the Employee. A simple fix is to limit the recursion depth, but a more robust approach involves tracking which objects you've already visited.
- You can maintain a
setof object IDs using Python's built-inid()function. - Before inspecting an object, check if its ID is already in the set. If it is, you've found a cycle and should skip it, preventing the infinite loop.
Handling missing attributes with getattr()
Directly accessing an object's attributes is a gamble. If an attribute doesn't exist, your program will crash with an AttributeError. This is a common trap when you're working with objects that have optional properties. The code below shows what happens when you try to access a missing attribute like user_obj.email.
def process_user(user_obj):
# Direct attribute access causes AttributeError
email = user_obj.email
print(f"Sending message to {email}")
class User:
def __init__(self, username):
self.username = username
user = User("john_doe")
process_user(user)
Because the User object is created without an email attribute, the direct access in process_user fails and raises an AttributeError. The corrected code below shows a more robust way to handle this situation.
def process_user(user_obj):
email = getattr(user_obj, 'email', '[email protected]')
print(f"Sending message to {email}")
class User:
def __init__(self, username):
self.username = username
user = User("john_doe")
process_user(user)
The corrected code demonstrates a robust solution using getattr(). By calling getattr(user_obj, 'email', '[email protected]'), you can safely request an attribute that might not exist. If the email attribute is missing, the function simply returns the default value you provided instead of raising an AttributeError. This technique is crucial when dealing with objects that have optional fields, like those from API responses or user-submitted forms, as it prevents your program from crashing.
Avoiding TypeError when using vars() with built-in types
The vars() function is great for inspecting custom objects, but it hits a wall with built-in types like strings or numbers. Since these types don't have a __dict__ attribute, calling vars() on them triggers a TypeError. The code below demonstrates this exact problem.
def print_attributes(obj):
# This will fail on built-in types
attributes = vars(obj)
print(attributes)
# Works for custom classes
class Person:
def __init__(self):
self.name = "Alice"
print_attributes(Person())
print_attributes("hello") # Will raise TypeError
The print_attributes function fails when called with "hello" because vars() cannot inspect built-in types like strings, causing a TypeError. The corrected code below demonstrates a simple way to prevent this from happening.
def print_attributes(obj):
if hasattr(obj, '__dict__'):
attributes = vars(obj)
print(attributes)
else:
print(f"Can't use vars() on {type(obj).__name__}")
print(f"Available attributes: {dir(obj)[:3]}...")
class Person:
def __init__(self):
self.name = "Alice"
print_attributes(Person())
print_attributes("hello") # Safely handles built-in types
The corrected code safely inspects objects by first checking `hasattr(obj, '__dict__')`. This simple guard clause prevents a `TypeError` because built-in types like strings don't have a `__dict__` attribute, which `vars()` requires.
This check is crucial when writing functions that might receive objects of unknown types. It allows your code to handle different inputs gracefully instead of crashing, making your application more robust and predictable.
Preventing infinite recursion in circular references
Recursive inspection functions can get trapped in an infinite loop when objects reference each other. For example, an employee object might point to its department, which in turn points back to the employee, creating a cycle that causes a RecursionError. The code below shows what happens when a simple recursive function tries to inspect this structure and runs into this exact problem.
def print_object_structure(obj, indent=0):
# This will crash with RecursionError for circular references
for attr_name, attr_value in vars(obj).items():
print(" " * indent + f"{attr_name}: {type(attr_value).__name__}")
if hasattr(attr_value, "__dict__"):
print_object_structure(attr_value, indent + 2)
class Department:
def __init__(self, manager=None):
self.manager = manager
class Employee:
def __init__(self, department=None):
self.department = department
manager = Employee()
dept = Department(manager)
manager.department = dept
print_object_structure(manager)
The print_object_structure function endlessly cycles between the Employee and Department objects because they reference each other, causing a RecursionError. The corrected code below demonstrates how to break this cycle.
def print_object_structure(obj, indent=0, visited=None):
if visited is None:
visited = set()
obj_id = id(obj)
if obj_id in visited:
print(" " * indent + "<circular reference>")
return
visited.add(obj_id)
for attr_name, attr_value in vars(obj).items():
print(" " * indent + f"{attr_name}: {type(attr_value).__name__}")
if hasattr(attr_value, "__dict__"):
print_object_structure(attr_value, indent + 2, visited)
class Department:
def __init__(self, manager=None):
self.manager = manager
class Employee:
def __init__(self, department=None):
self.department = department
manager = Employee()
dept = Department(manager)
manager.department = dept
print_object_structure(manager)
The corrected function avoids infinite loops by tracking which objects it has already inspected. It maintains a visited set to store the unique identifier of each object, which it gets using the id() function. Before processing an object, it checks if its ID is in the set. If it is, the function stops to break the cycle. This is a vital safeguard when you're working with complex data models where objects often refer back to each other.
Real-world applications
Beyond debugging and error handling, object inspection powers sophisticated features like custom JSON encoders and dynamic plugin systems.
Creating a custom JSON encoder with attribute inspection
By inspecting an object's attributes, you can create a custom JSON encoder that automatically serializes your class instances while filtering out private or internal data.
import json
class CustomJSONEncoder(json.JSONEncoder):
def default(self, obj):
if hasattr(obj, '__dict__'):
return {k: v for k, v in vars(obj).items()
if not k.startswith('_')}
return super().default(obj)
class User:
def __init__(self, name, email):
self.name = name
self.email = email
self._private = "hidden"
user = User("Alice", "[email protected]")
print(json.dumps(user, cls=CustomJSONEncoder, indent=2))
This CustomJSONEncoder gives you fine-grained control over how your objects are turned into JSON. It extends Python's default encoder and customizes the default method to handle your specific classes.
- It uses
hasattr(obj, '__dict__')to check if an object is a custom class instance. - If so, it converts the object to a dictionary using
vars()but cleverly filters out any attributes prefixed with an underscore, like_private.
This ensures only public data is included in the final JSON output, which is perfect for creating clean API responses.
Building a plugin system with dynamic method discovery
Object inspection allows you to build a plugin system that dynamically discovers and registers public methods from different modules at runtime.
class PluginManager:
def load_plugin(self, plugin_module):
plugin_methods = [attr for attr in dir(plugin_module)
if callable(getattr(plugin_module, attr))
and not attr.startswith('_')]
for method_name in plugin_methods:
print(f"Registered: {method_name}")
class ImagePlugin:
def resize_image(self, size):
print(f"Resizing to {size}")
def apply_filter(self, filter_name):
print(f"Applying {filter_name}")
def _internal_helper(self):
pass
pm = PluginManager()
pm.load_plugin(ImagePlugin())
The PluginManager uses a powerful list comprehension to scan the ImagePlugin object. This single line of code filters all of the object's attributes to find only the ones that are public, callable methods.
- It combines
dir()to list attributes,getattr()to retrieve them, andcallable()to check if they are functions. - Crucially, it ignores any attribute starting with an underscore, respecting the convention for private members.
This approach lets the PluginManager interact with any compatible object without needing to know its specific methods in advance.
Get started with Replit
Turn your knowledge into a real tool. Describe what you want to build, like "a script that generates API docs from a Python class" or "a config validator that checks for required attributes," and let Replit Agent handle it.
Replit Agent writes the code, tests for errors, and deploys your app. It handles the heavy lifting so you can focus on your idea. Start building with Replit.
Create and deploy websites, automations, internal tools, data pipelines and more in any programming language without setup, downloads or extra tools. All in a single cloud workspace with AI built in.
Create & deploy websites, automations, internal tools, data pipelines and more in any programming language without setup, downloads or extra tools. All in a single cloud workspace with AI built in.


.png)
.png)