Python-Specific Design Patterns
Python-Specific Design Patterns
5.1 Context Managers
Context managers in Python provide a clean way to manage resources, ensuring proper acquisition and release.
The with Statement
Implementing Custom Context Managers
Explanation of __exit__ parameters: - exc_type: The type of the exception that occurred, or None if no exception occurred. - exc_value: The exception instance, or None if no exception occurred. - traceback: A traceback object, or None if no exception occurred.
Using contextlib
Async Context Managers
5.2 Descriptors
Descriptors provide a way to customize attribute access in Python classes.
Meaningful Descriptor Example
Explanation of __get__ parameters: - obj: The instance that the attribute was accessed through, or None when the attribute is accessed through the class. - objtype: The class that was used to access the attribute. This is set even when called on an instance.
The difference between __get__ and getattr: - __get__ is a method of the descriptor object itself. It’s called when accessing an attribute that is a descriptor. - getattr is a built-in function that retrieves an attribute from an object. Inside __get__, we often use getattr to access the actual stored value.
Advanced Descriptor: Lazy Property
Now, let’s break down how this works step by step:
- When
@LazyPropertyis applied to thedatamethod inDataProcessor:- An instance of
LazyPropertyis created, with thedatamethod as itsfunctionattribute. - This
LazyPropertyinstance replaces thedatamethod in the class dictionary.
- An instance of
- When we create a
DataProcessorinstance:- No data is loaded yet. The
filenameis stored, butdatais not accessed.
- No data is loaded yet. The
- The first time
processor.datais accessed:- Python sees that
datais a descriptor (it has a__get__method) and callsLazyProperty.__get__(processor, DataProcessor). - Inside
__get__:- It checks if
obj(processor) is None. It’s not, so it continues. - It calls
self.function(obj), which is equivalent to calling the originaldatamethod. - This prints “Loading data…” and reads the file.
- The result is stored back into the
processorinstance usingsetattr(obj, self.name, value). - The value is returned.
- It checks if
- Python sees that
- The second time
processor.datais accessed:- Python first looks for an instance attribute named
data. - It finds one (because we set it in step 3d), so it returns that value directly.
- The
LazyProperty.__get__method is not called this time.
- Python first looks for an instance attribute named
The key point is that after the first access, the LazyProperty descriptor is effectively replaced by the computed value. This is why the “Loading data…” message only appears once.
These examples demonstrate how context managers and descriptors can be used to create more robust, efficient, and Pythonic code. Context managers excel at resource management, while descriptors offer powerful ways to customize attribute behavior.