mirror of
https://github.com/python/cpython.git
synced 2025-08-24 10:45:53 +00:00
Issue #11647: allow contextmanager objects to be used as decorators as described in the docs. Initial patch by Ysj Ray.
This commit is contained in:
parent
f77b74dd1b
commit
0ded3e307b
6 changed files with 50 additions and 10 deletions
|
@ -54,8 +54,12 @@ Functions provided:
|
||||||
the exception has been handled, and execution will resume with the statement
|
the exception has been handled, and execution will resume with the statement
|
||||||
immediately following the :keyword:`with` statement.
|
immediately following the :keyword:`with` statement.
|
||||||
|
|
||||||
contextmanager uses :class:`ContextDecorator` so the context managers it
|
:func:`contextmanager` uses :class:`ContextDecorator` so the context managers
|
||||||
creates can be used as decorators as well as in :keyword:`with` statements.
|
it creates can be used as decorators as well as in :keyword:`with` statements.
|
||||||
|
When used as a decorator, a new generator instance is implicitly created on
|
||||||
|
each function call (this allows the otherwise "one-shot" context managers
|
||||||
|
created by :func:`contextmanager` to meet the requirement that context
|
||||||
|
managers support multiple invocations in order to be used as decorators).
|
||||||
|
|
||||||
.. versionchanged:: 3.2
|
.. versionchanged:: 3.2
|
||||||
Use of :class:`ContextDecorator`.
|
Use of :class:`ContextDecorator`.
|
||||||
|
@ -155,6 +159,12 @@ Functions provided:
|
||||||
def __exit__(self, *exc):
|
def __exit__(self, *exc):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
As the decorated function must be able to be called multiple times, the
|
||||||
|
underlying context manager must support use in multiple :keyword:`with`
|
||||||
|
statements. If this is not the case, then the original construct with the
|
||||||
|
explicit :keyword:`with` statement inside the function should be used.
|
||||||
|
|
||||||
.. versionadded:: 3.2
|
.. versionadded:: 3.2
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -9,10 +9,23 @@ __all__ = ["contextmanager", "closing", "ContextDecorator"]
|
||||||
|
|
||||||
class ContextDecorator(object):
|
class ContextDecorator(object):
|
||||||
"A base class or mixin that enables context managers to work as decorators."
|
"A base class or mixin that enables context managers to work as decorators."
|
||||||
|
|
||||||
|
def _recreate_cm(self):
|
||||||
|
"""Return a recreated instance of self.
|
||||||
|
|
||||||
|
Allows otherwise one-shot context managers like
|
||||||
|
_GeneratorContextManager to support use as
|
||||||
|
decorators via implicit recreation.
|
||||||
|
|
||||||
|
Note: this is a private interface just for _GCM in 3.2 but will be
|
||||||
|
renamed and documented for third party use in 3.3
|
||||||
|
"""
|
||||||
|
return self
|
||||||
|
|
||||||
def __call__(self, func):
|
def __call__(self, func):
|
||||||
@wraps(func)
|
@wraps(func)
|
||||||
def inner(*args, **kwds):
|
def inner(*args, **kwds):
|
||||||
with self:
|
with self._recreate_cm():
|
||||||
return func(*args, **kwds)
|
return func(*args, **kwds)
|
||||||
return inner
|
return inner
|
||||||
|
|
||||||
|
@ -20,8 +33,15 @@ class ContextDecorator(object):
|
||||||
class _GeneratorContextManager(ContextDecorator):
|
class _GeneratorContextManager(ContextDecorator):
|
||||||
"""Helper for @contextmanager decorator."""
|
"""Helper for @contextmanager decorator."""
|
||||||
|
|
||||||
def __init__(self, gen):
|
def __init__(self, func, *args, **kwds):
|
||||||
self.gen = gen
|
self.gen = func(*args, **kwds)
|
||||||
|
self.func, self.args, self.kwds = func, args, kwds
|
||||||
|
|
||||||
|
def _recreate_cm(self):
|
||||||
|
# _GCM instances are one-shot context managers, so the
|
||||||
|
# CM must be recreated each time a decorated function is
|
||||||
|
# called
|
||||||
|
return self.__class__(self.func, *self.args, **self.kwds)
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
try:
|
try:
|
||||||
|
@ -92,7 +112,7 @@ def contextmanager(func):
|
||||||
"""
|
"""
|
||||||
@wraps(func)
|
@wraps(func)
|
||||||
def helper(*args, **kwds):
|
def helper(*args, **kwds):
|
||||||
return _GeneratorContextManager(func(*args, **kwds))
|
return _GeneratorContextManager(func, *args, **kwds)
|
||||||
return helper
|
return helper
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -350,13 +350,13 @@ class TestContextDecorator(unittest.TestCase):
|
||||||
|
|
||||||
|
|
||||||
def test_contextmanager_as_decorator(self):
|
def test_contextmanager_as_decorator(self):
|
||||||
state = []
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def woohoo(y):
|
def woohoo(y):
|
||||||
state.append(y)
|
state.append(y)
|
||||||
yield
|
yield
|
||||||
state.append(999)
|
state.append(999)
|
||||||
|
|
||||||
|
state = []
|
||||||
@woohoo(1)
|
@woohoo(1)
|
||||||
def test(x):
|
def test(x):
|
||||||
self.assertEqual(state, [1])
|
self.assertEqual(state, [1])
|
||||||
|
@ -364,6 +364,11 @@ class TestContextDecorator(unittest.TestCase):
|
||||||
test('something')
|
test('something')
|
||||||
self.assertEqual(state, [1, 'something', 999])
|
self.assertEqual(state, [1, 'something', 999])
|
||||||
|
|
||||||
|
# Issue #11647: Ensure the decorated function is 'reusable'
|
||||||
|
state = []
|
||||||
|
test('something else')
|
||||||
|
self.assertEqual(state, [1, 'something else', 999])
|
||||||
|
|
||||||
|
|
||||||
# This is needed to make the test actually run under regrtest.py!
|
# This is needed to make the test actually run under regrtest.py!
|
||||||
def test_main():
|
def test_main():
|
||||||
|
|
|
@ -14,8 +14,8 @@ from test.support import run_unittest
|
||||||
|
|
||||||
|
|
||||||
class MockContextManager(_GeneratorContextManager):
|
class MockContextManager(_GeneratorContextManager):
|
||||||
def __init__(self, gen):
|
def __init__(self, func, *args, **kwds):
|
||||||
_GeneratorContextManager.__init__(self, gen)
|
super().__init__(func, *args, **kwds)
|
||||||
self.enter_called = False
|
self.enter_called = False
|
||||||
self.exit_called = False
|
self.exit_called = False
|
||||||
self.exit_args = None
|
self.exit_args = None
|
||||||
|
@ -33,7 +33,7 @@ class MockContextManager(_GeneratorContextManager):
|
||||||
|
|
||||||
def mock_contextmanager(func):
|
def mock_contextmanager(func):
|
||||||
def helper(*args, **kwds):
|
def helper(*args, **kwds):
|
||||||
return MockContextManager(func(*args, **kwds))
|
return MockContextManager(func, *args, **kwds)
|
||||||
return helper
|
return helper
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -704,6 +704,7 @@ Burton Radons
|
||||||
Brodie Rao
|
Brodie Rao
|
||||||
Antti Rasinen
|
Antti Rasinen
|
||||||
Sridhar Ratnakumar
|
Sridhar Ratnakumar
|
||||||
|
Ysj Ray
|
||||||
Eric Raymond
|
Eric Raymond
|
||||||
Edward K. Ream
|
Edward K. Ream
|
||||||
Chris Rebert
|
Chris Rebert
|
||||||
|
|
|
@ -83,6 +83,10 @@ Core and Builtins
|
||||||
Library
|
Library
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
- Issue #11647: objects created using contextlib.contextmanager now support
|
||||||
|
more than one call to the function when used as a decorator. Initial patch
|
||||||
|
by Ysj Ray.
|
||||||
|
|
||||||
- logging: don't define QueueListener if Python has no thread support.
|
- logging: don't define QueueListener if Python has no thread support.
|
||||||
|
|
||||||
- functools.cmp_to_key() now works with collections.Hashable().
|
- functools.cmp_to_key() now works with collections.Hashable().
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue