Skip to main content

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}
info

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']
tip

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

Featuredir()vars()
ReturnsSorted list of namesdict of name-value pairs
Includes inherited membersYesNo
Shows valuesNo (names only)Yes (names and values)
Works on built-in typesYesNo
Works with __slots__YesNo
Primary useDiscovery and explorationDebugging 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.