Skip to main content
blog

Object Introspection in Python Explained

February 7, 2023 · 820 words · 4 min read

Object Introspection in Python ExplainedImage unavailableTry again
Table of Contents
Preparing narration.

What introspection means

Introspection means asking an object about itself while the program is running. It is useful when you are learning an unfamiliar class, checking why an attribute lookup behaves strangely, or debugging a value that is not what you expected.

Python gives you several built-ins for this. They answer different questions, so do not treat their output as a complete description of an object.

The questions are worth separating. type() tells you what kind of value you have. dir() tells you which names might be available. getattr() reads a name when you only know it at runtime. help() points you toward the documented interface. These tools are most useful when you use the smallest one that answers the question in front of you.

The dir() function lists names

dir(value) returns a sorted list of names that Python considers useful for that object. The list can include methods, attributes, and dunder names inherited from a class. It is a discovery tool, not a guarantee that every name can be called successfully.

Python
my_list = [1, 2, 3]
names = [name for name in dir(my_list) if name in {"append", "pop"}]
print(names)

The output is:

Text
['append', 'pop']

Use dir() when you know roughly what you are searching for. Reading the entire list for a large object can be noisy, so filtering it often makes the result easier to use.

The presence of a name does not prove that it is safe to call. A property may run code when read, and a method may need arguments. Treat dir() as a menu of possibilities, then inspect the signature or documentation before using an unfamiliar entry.

The dict attribute shows stored attributes

Many Python objects keep their instance attributes in a dictionary called __dict__. It is an attribute, not a function, so write person.__dict__, not person.__dict__().

Python
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age
 
 
person = Person("John", 30)
print(person.__dict__)

The output is:

Text
{'name': 'John', 'age': 30}

__dict__ shows attributes stored directly on this instance. A class can also have a __dict__, and some objects, including classes that use __slots__, do not expose an instance dictionary. If the attribute is missing, that does not mean the object has no state.

When the attribute name comes from a configuration value, use getattr() instead of constructing an expression:

Python
class Settings:
    timeout = 30
 
 
settings = Settings()
name = "timeout"
print(getattr(settings, name, 10))

The third argument is a default for a missing attribute. hasattr() can answer whether a lookup succeeds, but remember that it may execute a property and can hide an exception raised during that lookup. For code you control, a direct access with a clear AttributeError is often easier to debug.

The id() function identifies an object during its lifetime

id(value) returns an integer that stays the same for that object while it exists. Two live objects cannot have the same identity value. The exact number is implementation-dependent, so it may change between runs.

Use the is operator when you want to test identity:

Python
first = []
second = first
third = []
 
print(first is second)
print(first is third)
print(id(first) == id(second))

The output is:

Text
True
False
True

Do not use id() to compare equal values. Two separate lists can contain the same items while representing different objects. Use == for value comparison and is for identity comparison.

The help() function reads documentation

help(value) opens Python's built-in documentation for a value. It can show a module, class, function, method, or object. In an interactive shell, Python may open the result in a pager, so press q when you are done reading.

Python
help(str.upper)

The result includes the method's description and signature when that information is available. help() is especially handy when you remember a method name but not its arguments.

callable(value) is another small check that helps during exploration. It tells you whether Python can call the value, while isinstance(value, SomeType) lets you check whether it fits a type or protocol you explicitly care about. Avoid turning a debugging tool into a maze of type checks; in ordinary Python code, trying the operation and handling a meaningful error can be clearer.

A practical debugging routine

When an unfamiliar object appears in your code, start with type(value) to identify its class. Filter dir(value) to find possible names, inspect value.__dict__ when the object provides one, and use help() for the documentation. Use getattr() when the name is dynamic and id() only when the question is whether two names point to the same object.

My caveat is that introspection shows what Python exposes, not what the author intended. Treat the output as a clue, then read the class or docstring before changing code based on a guess. Printing an object's attributes can help you find a problem, but it is not a replacement for a documented interface or a test that explains the expected behavior.

Thank you placard concept illustrationImage unavailableTry againThank you placard concept illustration

pythonintrospectiondebuggingprogramming