Hello,
In this short article I would like to talk about context managers. I personally consider that at the core they are just a form of decorators. If you don’t know what a decorator is check the Decorator Pattern Wikipedia article.
Decorators can be used to implement cross-cutting concerns. We have componentA and we need logging and security, we could write the logic for logging and security handling in componentA but some people consider component a should be componentA not componentAthatAlsoKnowsAboutSecurityAndOtherStuff. Since it’s not the component’s responsibility to authorize requests or log calls to a external logging service, we can wrap the componentA into a decorator that does just that.
A formal definition for cross-cutting concerns as taken from Wikipedia is the following:
In aspect-oriented software development, cross-cutting concerns are aspects of a program that affect other concerns. These concerns often cannot be cleanly decomposed from the rest of the system in both the design and implementation, and can result in either scattering (code duplication), tangling (significant dependencies between systems), or both.
And some examples of cross cutting concerns include:
- Caching
- Data validation
- Error detection and correction
- Information security
- Logging
- Memory management
- Monitoring
- Persistence
Since the context managers are sort of similar to decorators you can use them to implement cross cutting concerns. Let’s explore.
Simple Example
In Python you can have two types of context managers: a function and a class. In order for the function to behave like a context manager it will need to be decorated with the @contextmanager decorator, and in order for a class behave like a context manager it needs to implement __enter
__ and __exit__
.
Context managers can be called using the with statement. The following code snippet demonstrates two context managers:
- One that logs when the function is called and when it exits.
- One that intercepts the function arguments.
|
|
Caching
What is caching? In short..
Caching is used to store the result of an expensive computation somewhere in memory or on a persistent storage device in order to optimize the program.
We have the compute_fibonacci function, which is quite slow. A version that uses cache has been implementing in the CachedComputeFibonacci class. Notice how the code takes some time to output the result for the first call of print(cached_compute_fibonacci(35))
statement but the second print in instant.
|
|
Logging
Logging can be useful for debugging and auditing purposes.
|
|
Error detection and correction
If you find yourself duplicating the same try/catch logic in multiple places of your code perhaps you can extract it into a context manager for handling errors:
|
|
The code is definitely more cleaner this way, in my opinion.
Thanks for reading and I hope that you’ve learnt something!