杰瑞科技汇

Python delegation如何实现?

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.

Python delegation如何实现?-图1
(图片来源网络,侵删)

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:

  1. Check the instance's __dict__.
  2. Check the class's __dict__.
  3. Check the parent classes' __dict__ (the MRO).
  4. 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.

Python delegation如何实现?-图2
(图片来源网络,侵删)

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?

Python delegation如何实现?-图3
(图片来源网络,侵删)
  • Decoupling: ReportGenerator doesn't care if the data comes from a file, an API, or a database. It only knows that its data_source has a get_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 DatabaseDataSource class. You don't need to modify ReportGenerator at 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 self inside it (e.g., return self.some_other_attr is fine).
  • Use Case: Perfect for delegating all "missing" methods and attributes to a helper object, as shown in the SecureList example.

__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.anything inside __getattribute__, you will call __getattribute__ again, leading to an infinite recursion and a RecursionError.
  • 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 a data_source with a get_data method) 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.
分享:
扫描分享到社交APP
上一篇
下一篇