YouTip LogoYouTip

Python Proxy

Proxy pattern is a structural design pattern that provides a surrogate or placeholder for another object to control access to it. Simply put, a proxy is an intermediary that acts as a mediator between the client and the target object. ### Real-life Proxy Analogies Imagine scenarios in real life: * **Real estate agent**: You don't need to communicate directly with the homeowner; through an agent, you can learn about property listings and arrange viewings * **Celebrity agent**: Manufacturers wanting to invite a celebrity for endorsement need to go through an agent to negotiate cooperation details * **Web proxy server**: Your network requests first go through a proxy server, which then forwards them to the target website In these examples, the agent/broker acts as the proxy, controlling access to the real object. * * * ## Core Components of Proxy Pattern Proxy pattern typically consists of three key roles: ### 1. Subject Defines the common interface for both Real Subject and Proxy, so that the proxy can be used anywhere the real subject is used. ### 2. Real Subject Defines the real object that the proxy represents, which is the object ultimately referenced. ### 3. Proxy Maintains a reference so the proxy can access the entity, provides an interface identical to the real subject, and controls access to the real subject. !(#) * * * ## Types of Proxy Patterns There are several variants of proxy pattern, each with different application scenarios: ### 1. Virtual Proxy Delays the creation of expensive objects until they are actually needed. ### 2. Protection Proxy Controls access to the original object, used when objects should have different access permissions. ### 3. Remote Proxy Provides a local representative for an object that resides in a different address space. ### 4. Smart Reference Proxy Performs additional operations when accessing an object, such as reference counting, lazy loading, etc. * * * ## Python Implementation of Proxy Pattern Let's understand the implementation of proxy pattern through concrete code examples. ### Basic Interface Definition First, we define the abstract subject interface: ## Example from abc import ABC, abstractmethod from typing import Any class Subject(ABC): """ Abstract subject interface, defining common operations for real subject and proxy """ @abstractmethod def request(self) -> Any: """Main method to execute requests""" pass ### Real Subject Implementation ## Example class RealSubject(Subject): """ Real subject class, containing core business logic Usually expensive to create and initialize """ def __init__ (self, name: str): self.name= name print(f"Creating RealSubject instance: {self.name} (This is an expensive operation)") def request(self) ->str: """Execute real request""" print(f"RealSubject: Processing request - {self.name}") return f"Response from {self.name}" ### Proxy Class Implementation ## Example class Proxy(Subject): """ Proxy class, controlling access to real subject Can add additional features such as lazy loading, access control, etc. """ def __init__ (self, subject_name: str): self.subject_name= subject_name self._real_subject =None# Delayed initialization def _lazy_init(self) ->None: """Lazy loading of real subject object""" if self._real_subject is None: self._real_subject = RealSubject(self.subject_name) def request(self) ->str: """Proxy request method, can add extra logic""" print("Proxy: Pre-processing before calling real object") # Lazy load real object self._lazy_init() # Call real object's method result =self._real_subject.request() print("Proxy: Post-processing after calling real object") return f"Proxy enhanced result: {result}" ### Client Code ## Example def client_code(subject: Subject) ->None: """ Client code, interacting with subject through abstract interface Doesn't know or care whether real subject or proxy is being used """ print("Client: Starting operation") result = subject.request() print(f"Client: Received result - {result}") print() # Usage example if __name__ =="__main__": print("Using real subject directly:") real_subject = RealSubject("Real Object A") client_code(real_subject) print("Using proxy:") proxy = Proxy("Proxy Object B") client_code(proxy) # Use the same proxy again, observe lazy loading effect print("Using the same proxy again:") client_code(proxy) Running the above code, you will see the following output: Using real subject directly:Creating RealSubject instance: Real Object A (This is an expensive operation)Client: Starting operationRealSubject: Processing request - Real Object A Client: Received result - Response from Real Object AUsing proxy:Client: Starting operationProxy: Pre-processing before calling real objectCreating RealSubject instance: Proxy Object B (This is an expensive operation)RealSubject: Processing request - Proxy Object B Proxy: Post-processing after calling real objectClient: Received result - Proxy enhanced result: Response from Proxy Object BUsing the same proxy again:Client: Starting operationProxy: Pre-processing before calling real objectRealSubject: Processing request - Proxy Object B Proxy: Post-processing after calling real objectClient: Received result - Proxy enhanced result: Response from Proxy Object B * * * ## Practical Application Example: Image Loading Proxy Let's look at a more practical exampleβ€”a virtual proxy for image loading. ### Image Loading System ## Example from pathlib import Path import time class Image: """Real image class, simulating expensive operation of loading large images""" def __init__ (self, filename: str): self.filename= filename self._load_image() def _load_image(self) ->None: """Simulate expensive operation of loading large images""" print(f"Loading image: {self.filename} (This may take a few seconds...)") time.sleep(2)# Simulate loading time print(f"Image {self.filename} loaded!") def display(self) ->None: """Display image""" print(f"Displaying image: {self.filename}") class ImageProxy: """Image proxy, implementing lazy loading""" def __init__ (self, filename: str): self.filename= filename self._image =None def display(self) ->None: """Display image, load if necessary""" if self._image is None: print("Proxy: Detected image not loaded, starting lazy loading...") self._image = Image(self.filename) else: print("Proxy: Image already loaded, displaying directly") self._image.display() # Usage example def demo_image_proxy(): print("Creating image proxy (will not load image immediately)") proxy = ImageProxy("large_photo.jpg") print("n First call to display() - triggers lazy loading:") proxy.display() print("n Second call to display() - uses already loaded image directly:") proxy.display() # Run example demo_image_proxy() * * * ## Protection Proxy Example: Access Control Protection proxy is used to control access to sensitive resources. ## Example class SensitiveData: """Sensitive data class""" def __init__ (self, data: str): self.data= data def read_data(self) ->str: """Read sensitive data""" return f"Sensitive data: {self.data}" class ProtectionProxy: """Protection proxy, implementing access control""" def __init__ (self, sensitive_data: SensitiveData, user_role: str): self._sensitive_data = sensitive_data self.user_role= user_role def read_data(self) ->str: """Read data, but perform permission check""" if self.user_role!="admin": return"Error: Insufficient permissions, only administrators can access sensitive data" return self._sensitive_data.read_data() # Usage example def demo_protection_proxy(): sensitive_data = SensitiveData("Confidential information: Project budget is 1 million") # Regular user attempts to access user_proxy = ProtectionProxy(sensitive_data,"user") print("Regular user attempting access:") print(user_proxy.read_data()) # Administrator access admin_proxy = ProtectionProxy(sensitive_data,"admin") print("n Administrator access:") print(admin_proxy.read_data()) demo_protection_proxy() * * * ## Pros and Cons of Proxy Pattern ### Advantages 1. **Control object access**: Proxy can control how and when clients access the real object 2. **Lazy loading optimization**: Virtual proxy can delay creation of expensive objects, improving performance 3. **Enhanced security**: Protection proxy can add access control logic 4. **Open/closed principle**: New proxies can be introduced without modifying client code 5. **Separation of concerns**: Proxy can handle auxiliary functions unrelated to core business logic ### Disadvantages 1. **Increased complexity**: Introduces new abstraction layer, making code structure more complex 2. **Response delay**: Proxy may increase request processing time 3. **Potential over-engineering**: Using proxy for simple scenarios may be unnecessary * * * ## Application Scenarios of Proxy Pattern Proxy pattern is particularly useful in the following scenarios: ### 1. Lazy Loading When object creation is costly but may not be immediately used. ### 2. Access Control When access to certain objects needs to be restricted. ### 3. Local Representation Providing local interfaces for remote objects, such as RPC calls. ### 4. Logging Adding logging before and after method calls. ### 5. Caching Providing caching for results of expensive operations. * * * ## Practice Exercises To consolidate understanding of proxy pattern, try completing the following exercises: ### Exercise 1: Cache Proxy Create a cache proxy that adds caching functionality for a Fibonacci sequence calculation function: ## Example class FibonacciCalculator: """Calculate Fibonacci sequence""" def fibonacci(self, n: int) ->int: if n int: # Implement caching logic pass # Test code def test_cache_proxy(): proxy = FibonacciCacheProxy() print("First calculation of fib(10):") result1 = proxy.fibonacci(10) print(f"Result: {result1}") print("Second calculation of fib(10) (should be retrieved from cache):") result2 = proxy.fibonacci(10) print(f"Result: {result2}") print(f"Both results are same: {result1 == result2}") ### Exercise 2: Logging Proxy Create a logging proxy that records method call information: ## Example class DatabaseService: """Database service""" def query(self, sql: str) ->str: return f"Executing query: {sql}" def update(self, sql: str) ->str: return f"Executing update: {sql}" # Your task: Implement LoggingProxy class # Requirement: Record call time, parameters, and results for each method class LoggingProxy: def __init__ (self): self._service = DatabaseService() def query(self, sql: str) ->str: # Add logging logic pass def update(self, sql: str) ->str: # Add logging logic pass * * * ## Summary Proxy pattern is a powerful design pattern that controls access to real objects by introducing an intermediary layer. Implementing proxy pattern in Python is relatively simple, thanks mainly to Python's dynamic features. ### Key Points 1. **Proxy is intermediary**: Acts as a middleman between client and real object 2. **Consistent interface**: Proxy and real object implement the same interface 3. **Controlled access**: Proxy can add additional control logic such as lazy loading, permission checks, etc. 4. **Flexible application**: Choose different types of proxies based on requirements
← Python BridgePython Adapter β†’