diff options
author | Nick Coghlan <ncoghlan@gmail.com> | 2011-05-05 13:49:25 (GMT) |
---|---|---|
committer | Nick Coghlan <ncoghlan@gmail.com> | 2011-05-05 13:49:25 (GMT) |
commit | 0ded3e307b188246018c893d90f26dfba6fe282c (patch) | |
tree | e38e7fb0d4d1a7de987713b7ea540dfdf00ea230 /Lib | |
parent | f77b74dd1beacb69b6853303aa70a0de64cd71ae (diff) | |
download | cpython-0ded3e307b188246018c893d90f26dfba6fe282c.zip cpython-0ded3e307b188246018c893d90f26dfba6fe282c.tar.gz cpython-0ded3e307b188246018c893d90f26dfba6fe282c.tar.bz2 |
Issue #11647: allow contextmanager objects to be used as decorators as described in the docs. Initial patch by Ysj Ray.
Diffstat (limited to 'Lib')
-rw-r--r-- | Lib/contextlib.py | 28 | ||||
-rw-r--r-- | Lib/test/test_contextlib.py | 7 | ||||
-rw-r--r-- | Lib/test/test_with.py | 6 |
3 files changed, 33 insertions, 8 deletions
diff --git a/Lib/contextlib.py b/Lib/contextlib.py index 4633cff..e90fc41 100644 --- a/Lib/contextlib.py +++ b/Lib/contextlib.py @@ -9,10 +9,23 @@ __all__ = ["contextmanager", "closing", "ContextDecorator"] class ContextDecorator(object): "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): @wraps(func) def inner(*args, **kwds): - with self: + with self._recreate_cm(): return func(*args, **kwds) return inner @@ -20,8 +33,15 @@ class ContextDecorator(object): class _GeneratorContextManager(ContextDecorator): """Helper for @contextmanager decorator.""" - def __init__(self, gen): - self.gen = gen + def __init__(self, func, *args, **kwds): + 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): try: @@ -92,7 +112,7 @@ def contextmanager(func): """ @wraps(func) def helper(*args, **kwds): - return _GeneratorContextManager(func(*args, **kwds)) + return _GeneratorContextManager(func, *args, **kwds) return helper diff --git a/Lib/test/test_contextlib.py b/Lib/test/test_contextlib.py index d6bb5b8..6e38305 100644 --- a/Lib/test/test_contextlib.py +++ b/Lib/test/test_contextlib.py @@ -350,13 +350,13 @@ class TestContextDecorator(unittest.TestCase): def test_contextmanager_as_decorator(self): - state = [] @contextmanager def woohoo(y): state.append(y) yield state.append(999) + state = [] @woohoo(1) def test(x): self.assertEqual(state, [1]) @@ -364,6 +364,11 @@ class TestContextDecorator(unittest.TestCase): test('something') 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! def test_main(): diff --git a/Lib/test/test_with.py b/Lib/test/test_with.py index a9d374b..e8cc8c0 100644 --- a/Lib/test/test_with.py +++ b/Lib/test/test_with.py @@ -14,8 +14,8 @@ from test.support import run_unittest class MockContextManager(_GeneratorContextManager): - def __init__(self, gen): - _GeneratorContextManager.__init__(self, gen) + def __init__(self, func, *args, **kwds): + super().__init__(func, *args, **kwds) self.enter_called = False self.exit_called = False self.exit_args = None @@ -33,7 +33,7 @@ class MockContextManager(_GeneratorContextManager): def mock_contextmanager(func): def helper(*args, **kwds): - return MockContextManager(func(*args, **kwds)) + return MockContextManager(func, *args, **kwds) return helper |