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
@LazyProperty
is applied to thedata
method inDataProcessor
:- An instance of
LazyProperty
is created, with thedata
method as itsfunction
attribute. - This
LazyProperty
instance replaces thedata
method in the class dictionary.
- An instance of
- When we create a
DataProcessor
instance:- No data is loaded yet. The
filename
is stored, butdata
is not accessed.
- No data is loaded yet. The
- The first time
processor.data
is accessed:- Python sees that
data
is 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 originaldata
method. - This prints “Loading data…” and reads the file.
- The result is stored back into the
processor
instance usingsetattr(obj, self.name, value)
. - The value is returned.
- It checks if
- Python sees that
- The second time
processor.data
is 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.