Great idea! Let's explore the dir() function on objects, specifically using the Person class from this step's example.
The dir() function, when called on an object, returns a list of all valid attributes (methods and data members) of that object. This includes attributes defined directly on the object's class, attributes inherited from parent classes, and attributes added dynamically to the instance.
Let's use the Person object p from your step5_introspection.py file:
# Assuming you have run the code in step5_introspection.py
# If not, let's redefine the Person class and instance quickly:
class Person:
species = "Homo sapiens"
def __init__(self, name, age):
self.name = name
self.age = age
p = Person("Alice", 30)
setattr(p, 'height', 175) # After this line, 'height' is added
Now, if you were to call dir(p):
print(dir(p))
You would see a long list of attributes. Let's look at what some of the key ones represent:
-
Special (Dunder) Methods: You'll notice many attributes starting and ending with double underscores (e.g.,
__init__,__str__,__dict__,__class__). These are special methods (often called "dunder methods" for "double underscore") that Python uses internally to define behavior for things like object initialization, string representation, attribute access, and more. While they are attributes, you typically don't call them directly but rather through Python's syntax (e.g.,Person("Alice", 30)implicitly calls__init__). -
Instance Attributes: You'll see
name,age, andheight. These are the attributes specific to thepinstance.nameandageare set in the__init__method.heightwas added dynamically usingsetattr(p, 'height', 175).
-
Class Attributes: You'll also find
species, which is a class-level attribute.
Example and Interpretation:
Let's run a small interactive session (or you can add these lines to your step5_introspection.py file):
# Inside step5_introspection.py after setattr(p, 'height', 175)
p_attributes = dir(p)
print(f"\nAll attributes of 'p' (using dir(p)): {p_attributes}")
# Let's filter for user-defined or dynamically added attributes for clarity
print("\nUser-defined or dynamically added attributes:")
for attr in p_attributes:
if not attr.startswith('__') and not callable(getattr(p, attr, None)): # Exclude dunders & methods
print(f" - {attr}: {getattr(p, attr)}")
print(f"\nIs 'name' in dir(p)? {'name' in dir(p)}")
print(f"Is 'species' in dir(p)? {'species' in dir(p)}")
print(f"Is 'height' in dir(p)? {'height' in dir(p)}")
Output (simplified excerpt for dir(p)):
(The full output of dir(p) would be much longer, including all inherited object methods)
['__class__', '__delattr__', ..., 'age', 'height', 'name', 'species']
Then, from the added print statements:
All attributes of 'p' (using dir(p)): ['__class__', '__delattr__', ... 'age', 'height', 'name', 'species'] # This list will be very long
User-defined or dynamically added attributes:
- species: Homo sapiens
- name: Alice
- age: 30
- height: 175
Is 'name' in dir(p)? True
Is 'species' in dir(p)? True
Is 'height' in dir(p)? True
As you can see, dir(p) gives you a complete list of everything you can access via the dot notation (e.g., p.name, p.species, p.height, or even p.__class__). It's a powerful tool for discovering what an object can do or what data it holds, especially when you're working with unfamiliar objects or performing dynamic operations.
Feel free to experiment with dir() on other objects, like lists, strings, or even modules, to see what kind of attributes they expose!