Python provides powerful built-in functions like dir(), getattr(), and setattr() that allow you to explore, access, and modify object attributes dynamically. These tools are particularly useful when working with custom objects, debugging, or when your code needs to interact with objects in a flexible way. In this article, we’ll walk through these functions using a simple custom class, and demonstrate their practical applications.

Introducing a Simple Custom Class

Let’s start by defining a basic class that we’ll use throughout this article:

class MyObject:
    def __init__(self, name, value):
        self.name = name
        self.value = value
    
    def greet(self):
        return f"Hello, {self.name}!"
    
    def increment(self, amount):
        self.value += amount
        return self.value

Here, MyObject has two attributes, name and value, and two methods, greet() and increment().

Now, let’s create an instance of this class:

obj = MyObject(name="Alice", value=10)

Exploring Attributes and Methods with dir()

The dir() function returns a list of all attributes and methods associated with an object. This is useful when you want to explore what functionalities an object has.

print(dir(obj))
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'greet', 'increment', 'name', 'value']

This will output a list of attributes like name, value, greet, increment, along with inherited attributes and methods from Python’s base object class.

Accessing Attributes and Methods Dynamically with getattr()

While dir() tells you what’s available, getattr() lets you access these attributes and methods dynamically. This is particularly handy when the attribute names are stored as strings or generated at runtime.

For example, to access the greet method and the name attribute:

greet_method = getattr(obj, 'greet')
print(greet_method())  # Outputs: Hello, Alice!

name_value = getattr(obj, 'name')
print(name_value)  # Outputs: Alice
Hello, Alice!
Alice

getattr() allows you to dynamically choose which attribute or method to access, making your code more flexible.

Modifying Attributes Dynamically with setattr()

Just as getattr() allows dynamic access, setattr() allows dynamic modification of attributes. This is useful when you need to update attributes based on runtime conditions.

For example, let’s change the name attribute:

setattr(obj, 'name', 'Bob')
print(obj.name)  # Outputs: Bob
Bob

This can also be used to update other attributes:

setattr(obj, 'value', 20)
print(obj.value)  # Outputs: 20
20

Understanding the Difference Between setattr() and Direct Assignment

At first glance, setattr(obj, ’name’, ‘Bob’) might look similar to directly assigning obj.name = ‘Bob’. The difference is in flexibility:

  • Direct Assignment (obj.name = ‘Bob’): This is straightforward and should be used when the attribute name is known at the time of coding.

  • Dynamic Assignment (setattr(obj, ’name’, ‘Bob’)): This is more flexible, allowing you to set attributes when the name might not be known until runtime.

Here’s an example of how setattr() can be particularly useful:

attr_name = 'name'
setattr(obj, attr_name, 'Charlie')
print(obj.name)  # Outputs: Charlie
Charlie

Combining dir(), getattr(), and setattr() for Dynamic Attribute Handling

Let’s put it all together in a practical example. Suppose you want to explore all attributes of obj, print their values, and modify them dynamically if they are strings or integers:

for attribute in dir(obj):
    try:
        if attribute.startswith('__') and attribute.endswith('__'):
            # Skip special methods/attributes
            continue

        attr_value = getattr(obj, attribute)
        
        if callable(attr_value):
            # Skip methods that require arguments
            if attribute in ['increment']:
                print(f"Skipping {attribute}: requires arguments")
                continue
            print(f"{attribute}: {attr_value()} (Callable Method)")
        else:
            print(f"{attribute}: {attr_value}")
            
            # Modify the attribute if it's not a method
            if isinstance(attr_value, (int, str)):
                new_value = attr_value if isinstance(attr_value, str) else attr_value + 10
                setattr(obj, attribute, new_value)
                print(f"Updated {attribute}: {getattr(obj, attribute)}")
    except Exception as e:
        print(f"Error accessing {attribute}: {e}")
greet: Hello, Charlie! (Callable Method)
Skipping increment: requires arguments
name: Charlie
Updated name: Charlie
value: 20
Updated value: 30

This script:

  • Loops through all attributes using dir().
  • Uses getattr() to access each attribute.
  • Prints the value of each attribute.
  • Uses setattr() to update integer and string attributes dynamically.

Conclusion

Python’s dir(), getattr(), and setattr() are invaluable tools for dynamically exploring, accessing, and modifying object attributes. By using these functions with simple custom objects like MyObject, you can write more flexible and adaptive code. Whether you’re debugging, handling dynamic data, or writing general-purpose functions, these tools help you interact with objects in powerful ways. It is also a great learning tool when you are trying to learn something. It will help you explore all available attributes.

Tip: Try this with fig, ax objects in matplotlib. There are often several attributes that we can find that will give you a great way to manipulate your figures and axes.