Use the same function as context manager and decorator
Posted on 2022-09-25 in Trucs et astuces
I recently learned that context managers created with @contextmanager can be used either as a context manager or a decorator:
from contextlib import contextmanager @contextmanager def test_context(): print('Entering') yield print('Leaving') with test_context(): print('Inside') @test_context() def test_decorated(): print('Decorated') test_decorated()
We will yield:
Entering Inside Leaving Entering Decorated Leaving
In both cases, we can pass argument to our context manager/decorator like this:
from contextlib import contextmanager @contextmanager def test_context(value): print('Entering:', value) yield print('Leaving') with test_context('Hello there!'): print('Inside') @test_context('Hello there!') def test_decotared(): print('Decorated')
But what if we need to access the arguments of the function inside the decorator? When used as a context manager, we have to pass them, but we cannot access them directly when we use it as a decorator. To do this, we need to take inspiration from contextlib.ContextDecorator (what contextlib.contextmanager is using) to to some work ourselves:
from functools import wraps class test_context: def __init__(self, option_value=None, function_value=None): self._option_value = option_value self._function_value = function_value def __enter__(self): # Called when entering the ctx manager. print('Entering:', self._option_value, self._function_value) return self def __exit__(self, *exc): # Called when leaving the ctx manager. print('Leaving') def __call__(self, func): # Called when used as a decorator. @wraps(func) def wrapper(function_value): self._function_value = function_value with self._recreate_cm(): return func(function_value) return wrapper def _recreate_cm(self): # Taken as is from contextlib.ContextDecorator. return self with test_context('Option value', 'Function value'): print('Inside') @test_context('Option value') def test_decorated(value): print('Decorated', value) test_decorated('Function value')
This will yield:
Entering: Option value Function value Inside Leaving Entering: Option value Function value Decorated Function value Leaving