When to Use dir() and vars() in Python
Both dir() and vars() help you inspect Python objects, but they answer different questions and return different data types. dir() tells you what you can do with an object by listing all accessible attribute and method names. vars() tells you what data is stored in an object by returning its instance attributes as a dictionary.
Understanding when to use each makes debugging, exploration, and introspection more effective.
This guide explains both functions with practical examples, shows where vars() fails, and demonstrates how to combine them for comprehensive object inspection.
Using dir() for Discovery
The dir() function returns a sorted list of all attribute and method names accessible on an object, including members inherited from parent classes:
class Animal:
species = "unknown"
def speak(self):
pass
class Dog(Animal):
def bark(self):
pass
dog = Dog()
dog.name = "Rex"
# Show only non-dunder attributes for clarity
public_attrs = [attr for attr in dir(dog) if not attr.startswith("_")]
print(public_attrs)
Output:
['bark', 'name', 'speak', 'species']
Notice that dir() includes speak and species from the parent class Animal, bark from Dog, and name which was added to the instance. It answers the question: "What can I access on this object?"
When called without arguments, dir() returns the names available in the current local scope:
x = 10
y = "hello"
local_names = [name for name in dir() if not name.startswith("_")]
print(local_names)
Output:
['x', 'y']
Using vars() for State Inspection
The vars() function returns the object's __dict__ attribute, which is a dictionary of the instance's own stored data as key-value pairs:
class User:
def __init__(self, name, age):
self.name = name
self.age = age
def greet(self):
return f"Hello, {self.name}"
user = User("Alice", 30)
user.email = "alice@example.com"
print(vars(user))
Output:
{'name': 'Alice', 'age': 30, 'email': 'alice@example.com'}
Unlike dir(), vars() shows only the data stored directly on the instance with actual values. It does not include methods or inherited attributes. It answers the question: "What data does this specific instance hold right now?"
When called without arguments, vars() returns the local namespace as a dictionary:
def example():
a = 1
b = 2
print(vars())
example()
Output:
{'a': 1, 'b': 2}
Side-by-Side Comparison
The difference becomes clear when both are applied to the same object:
class Config:
default_timeout = 30 # Class attribute
def __init__(self):
self.host = "localhost"
self.port = 8080
config = Config()
# dir() shows everything accessible, including class attributes
accessible = [attr for attr in dir(config) if not attr.startswith("_")]
print(f"dir() result: {accessible}")
# vars() shows only instance data
print(f"vars() result: {vars(config)}")
Output:
dir() result: ['default_timeout', 'host', 'port']
vars() result: {'host': 'localhost', 'port': 8080}
Notice that vars() does not include default_timeout because it is a class attribute, not an instance attribute. It lives on the Config class object, not on the individual config instance. The dir() function includes it because it is accessible through the instance via Python's attribute lookup chain.
When vars() Fails
Not all objects have a __dict__ attribute. Built-in types and objects that use __slots__ raise a TypeError when passed to vars():
# Built-in types do not have __dict__
my_list = [1, 2, 3]
try:
vars(my_list)
except TypeError as e:
print(f"List error: {e}")
# Objects using __slots__ also lack __dict__
class Point:
__slots__ = ["x", "y"]
def __init__(self, x, y):
self.x = x
self.y = y
p = Point(10, 20)
try:
vars(p)
except TypeError as e:
print(f"Slots error: {e}")
# dir() works on everything
print(f"\ndir(my_list) includes: {[a for a in dir(my_list) if a.startswith('app')]}")
print(f"dir(p) includes: {[a for a in dir(p) if not a.startswith('_')]}")
Output:
List error: vars() argument must have __dict__ attribute
Slots error: vars() argument must have __dict__ attribute
dir(my_list) includes: ['append']
dir(p) includes: ['x', 'y']
If you are unsure whether vars() will work on a particular object, check with hasattr(obj, '__dict__') first, or wrap the call in a try/except block.
Debugging with Both Functions
Combining dir() and vars() gives you a comprehensive view of any object. Here is a utility function that uses both:
def inspect_object(obj):
"""Display an object's type, stored data, and available methods."""
print(f"Type: {type(obj).__name__}")
# Instance state via vars()
try:
state = vars(obj)
print(f"Instance data: {state}")
except TypeError:
print("Instance data: (no __dict__)")
# Available methods via dir()
methods = [
m for m in dir(obj)
if not m.startswith("_") and callable(getattr(obj, m))
]
print(f"Methods: {methods}")
class Service:
def __init__(self, name):
self.name = name
self.running = False
def start(self):
self.running = True
def stop(self):
self.running = False
inspect_object(Service("api"))
Output:
Type: Service
Instance data: {'name': 'api', 'running': False}
Methods: ['start', 'stop']
This pattern is useful during development and debugging. vars() shows you the current state of the object, while dir() with a callable() filter reveals what operations are available.
Inspecting Modules
Both functions work on modules as well, which is helpful when exploring an unfamiliar library:
import json
# dir() shows all public names exported by the module
public_names = [name for name in dir(json) if not name.startswith("_")]
print(f"json public names: {public_names}")
# vars() returns the module's full namespace as a dictionary
namespace_keys = [k for k in vars(json).keys() if not k.startswith("_")]
print(f"json namespace keys: {namespace_keys}")
Output:
json public names: ['JSONDecodeError', 'JSONDecoder', 'JSONEncoder', 'codecs', 'decoder', 'detect_encoding', 'dump', 'dumps', 'encoder', 'load', 'loads', 'scanner']
json namespace keys: ['scanner', 'decoder', 'JSONDecoder', 'JSONDecodeError', 'encoder', 'JSONEncoder', 'codecs', 'dump', 'dumps', 'detect_encoding', 'load', 'loads']
For modules, dir() and vars() produce similar results since a module's accessible names and its stored namespace are essentially the same thing. The difference is that dir() returns a sorted list of names while vars() returns a dictionary with the actual values.
Quick Reference
| Feature | dir() | vars() |
|---|---|---|
| Returns | Sorted list of names | dict of name-value pairs |
| Includes inherited members | Yes | No |
| Shows values | No (names only) | Yes (names and values) |
| Works on built-in types | Yes | No |
Works with __slots__ | Yes | No |
| Primary use | Discovery and exploration | Debugging and state inspection |
Summary
Use dir() when exploring unfamiliar objects or libraries to discover all available attributes and methods, including those inherited from parent classes.
Use vars() when debugging to see the actual data stored directly on an object instance, returned as a dictionary with both names and values.
Remember that vars() only works on objects that have a __dict__ attribute, so it fails on built-in types and objects using __slots__, while dir() works on everything.
Together, the two functions provide a complete picture of what an object can do and what it currently holds.