Of course! In Python, delegation is a powerful and elegant design pattern where an object, instead of performing a task itself, hands over (delegates) that task to a helper object. This is a core concept of Object-Oriented Programming (OOP) and is closely related to the Composition pattern.

The primary mechanism for implementing delegation in Python is the special __getattr__ method.
The Core Idea: Delegation vs. Inheritance
It's helpful to understand delegation by contrasting it with the more common OOP concept: inheritance.
| Feature | Inheritance | Delegation |
|---|---|---|
| Relationship | An "is-a" relationship (e.g., a Manager is a Employee). |
A "has-a" or "uses-a" relationship (e.g., a Manager has a ReportGenerator). |
| Mechanism | The child class automatically gets all attributes and methods from the parent class. | The parent class explicitly calls methods on a contained helper object. |
| Flexibility | Rigid. Changing the parent class can affect all child classes. | Flexible. You can change the helper object at runtime to change behavior. |
| Coupling | High coupling. The child class is tightly bound to the parent's implementation. | Low coupling. The main class only knows about the helper's public interface. |
How Delegation Works in Python: The __getattr__ Method
When you try to access an attribute on an object (e.g., my_object.some_attribute), Python follows a specific lookup order:
- Check the instance's
__dict__. - Check the class's
__dict__. - Check the parent classes'
__dict__(the MRO). - If the attribute is still not found, Python calls the
__getattr__method on the object.
This is the key to delegation. We can override __getattr__ to catch requests for attributes that our class doesn't have and then forward them to our helper object.

Example: A Simple Proxy
Let's create a SecureList that acts like a regular Python list but logs every access.
class SecureList:
def __init__(self, items):
# The helper object is a standard list
self._internal_list = list(items)
# Delegate all other attribute access to the internal list
def __getattr__(self, name):
print(f"Delegating access to attribute '{name}' to the internal list.")
# Forward the request to the helper object
return getattr(self._internal_list, name)
# We can also override specific methods to add custom behavior
def __getitem__(self, index):
print(f"Accessing item at index {index}.")
return self._internal_list[index]
def __setitem__(self, index, value):
print(f"Setting item at index {index} to {value}.")
self._internal_list[index] = value
# --- Usage ---
secure_data = SecureList([1, 2, 3, 4])
# Accessing methods that we DIDN'T override in SecureList
# This will trigger __getattr__
print(len(secure_data)) # Output: Delegating access to attribute '__len__' to the internal list. \n 4
# Accessing methods that we DID override in SecureList
# This will call our custom __getitem__
print(secure_data[0]) # Output: Accessing item at index 0. \n 1
# Adding an item (delegated via __getattr__)
secure_data.append(5)
print(secure_data._internal_list) # Output: [1, 2, 3, 4, 5]
# Checking the length again (delegated via __getattr__)
print(len(secure_data)) # Output: Delegating access to attribute '__len__' to the internal list. \n 5
In this example, SecureList doesn't know how to append, pop, or calculate its own len. It simply delegates these responsibilities to its _internal_list helper via __getattr__.
A More Practical Example: A ReportGenerator
Imagine you have different data sources (e.g., a database, a CSV file) and you want to generate a report in a consistent format. You can use delegation to create a generic ReportGenerator that works with any data source, as long as the data source provides a get_data() method.
import csv
# The helper object (the "delegate")
class CSVDataSource:
def __init__(self, filename):
self.filename = filename
def get_data(self):
print(f"Reading data from {self.filename}...")
with open(self.filename, 'r') as f:
reader = csv.reader(f)
return list(reader)
# Another helper object
class APIDataSource:
def __init__(self, api_url):
self.api_url = api_url
def get_data(self):
print(f"Fetching data from {self.api_url}...")
# In a real scenario, you'd use requests.get() here
return [["ID", "Name"], ["1", "Alice"], ["2", "Bob"]]
# The main class that uses delegation
class ReportGenerator:
def __init__(self, data_source):
# We are given a data source and store it
self.data_source = data_source
def generate_report(self):
# We delegate the task of getting data to our data source
data = self.data_source.get_data()
print("\n--- Report ---")
for row in data:
print(" | ".join(row))
print("--- End of Report ---\n")
# --- Usage ---
# Create a report from a CSV file
csv_source = CSVDataSource("data.csv") # Assume data.csv exists
report_from_csv = ReportGenerator(csv_source)
report_from_csv.generate_report()
# Create a report from an API
api_source = APIDataSource("https://api.example.com/data")
report_from_api = ReportGenerator(api_source)
report_from_api.generate_report()
Why is this design good?

- Decoupling:
ReportGeneratordoesn't care if the data comes from a file, an API, or a database. It only knows that itsdata_sourcehas aget_data()method. This is the Dependency Inversion Principle in action. - Flexibility & Extensibility: To support a new data source (e.g., a database), you just create a new
DatabaseDataSourceclass. You don't need to modifyReportGeneratorat all.
The Modern Alternative: __getattr__ vs. __getattribute__
Python 3 introduced a more explicit way to handle attribute access delegation that can be safer and more readable: using __getattribute__.
__getattr__ (The Classic Way)
- Called only if an attribute is not found through the normal lookup process.
- Safe: You can't accidentally create infinite loops by accessing
selfinside it (e.g.,return self.some_other_attris fine). - Use Case: Perfect for delegating all "missing" methods and attributes to a helper object, as shown in the
SecureListexample.
__getattribute__ (The Modern Way for Delegation)
- Called for every attribute access, regardless of whether it exists or not.
- Dangerous: If you try to access
self.anythinginside__getattribute__, you will call__getattribute__again, leading to an infinite recursion and aRecursionError. - The Solution: You must explicitly use
object.__getattribute__(self, 'name')to bypass your own method and access the attribute directly. - Use Case: More control. It's useful when you want to intercept all attribute access, not just the ones that are missing. It's the modern, preferred way to implement delegation if you are not using a library.
Example of Delegation with __getattribute__
class ModernSecureList:
def __init__(self, items):
self._internal_list = list(items)
def __getattribute__(self, name):
# Intercept access to ALL attributes
if name == '_internal_list':
# Special case: return our own attribute to avoid recursion
return object.__getattribute__(self, '_internal_list')
# For everything else, delegate
print(f"Modern Delegation: Accessing '{name}' via the internal list.")
internal_list = object.__getattribute__(self, '_internal_list')
return getattr(internal_list, name)
# --- Usage ---
modern_secure_data = ModernSecureList([10, 20, 30])
print(modern_secure_data[0]) # Output: Modern Delegation: Accessing '__getitem__' via the internal list. \n 10
print(len(modern_secure_data)) # Output: Modern Delegation: Accessing '__len__' via the internal list. \n 3
When to Use Delegation
- Adapting an Interface: When you want an object to behave like another object but with slight modifications (the "Proxy" pattern).
- Implementing Composition: When you want to build complex objects by combining simpler ones. The main class delegates tasks to its component parts.
- Following the Dependency Inversion Principle: When you want high-level modules (like
ReportGenerator) to depend on abstractions (like adata_sourcewith aget_datamethod) rather than low-level implementations. - Avoiding deep inheritance hierarchies. Inheritance can lead to fragile and complex base classes. Delegation offers a more flexible alternative.
Summary
| Method | When it's Called | Key Feature | Best For |
|---|---|---|---|
__getattr__ |
When an attribute is not found. | Safe, only handles missing attributes. | Classic, simple delegation of all unknown methods to a helper object. |
__getattribute__ |
On every attribute access. | Powerful but requires object.__getattribute__ to avoid recursion. |
Modern, explicit delegation where you need to intercept all access. |
__setattr__ |
On every attribute assignment. | Similar to __getattribute__, must use object.__setattr__. |
Controlling how attributes are set on an object. |
