Skip to content

When to Use dir() and vars() in Python

Learn how to use Python's dir() and vars() functions for object introspection, when to inspect available attributes and methods versus examining instance state.

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"

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()

accessible = [attr for attr in dir(config) if not attr.startswith("_")]
print(f"dir() result: {accessible}")

print(f"vars() result: {vars(config)}")

Output:

dir() result: ['default_timeout', 'host', 'port']
vars() result: {'host': 'localhost', 'port': 8080}

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():

my_list = [1, 2, 3]
try:
    vars(my_list)
except TypeError as e:
    print(f"List error: {e}")

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}")

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

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__}")

    try:
        state = vars(obj)
        print(f"Instance data: {state}")
    except TypeError:
        print("Instance data: (no __dict__)")

    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

public_names = [name for name in dir(json) if not name.startswith("_")]
print(f"json public names: {public_names}")

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.

Navigation