diff --git a/Lib/contextlib.py b/Lib/contextlib.py index c53b35e8d5..a410696f15 100644 --- a/Lib/contextlib.py +++ b/Lib/contextlib.py @@ -54,35 +54,9 @@ class ContextDecorator(object): return inner -class _GeneratorContextManagerBase: - """Shared functionality for @contextmanager and @asynccontextmanager.""" - - def __init__(self, func, args, kwds): - self.gen = func(*args, **kwds) - self.func, self.args, self.kwds = func, args, kwds - # Issue 19330: ensure context manager instances have good docstrings - doc = getattr(func, "__doc__", None) - if doc is None: - doc = type(self).__doc__ - self.__doc__ = doc - # Unfortunately, this still doesn't provide good help output when - # inspecting the created context manager instances, since pydoc - # currently bypasses the instance docstring and shows the docstring - # for the class instead. - # See http://bugs.python.org/issue19404 for more details. - - -class _GeneratorContextManager(_GeneratorContextManagerBase, - AbstractContextManager, - ContextDecorator): +class _GeneratorContextManager(AbstractContextManager): """Helper for @contextmanager decorator.""" - 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: return next(self.gen) @@ -137,9 +111,50 @@ class _GeneratorContextManager(_GeneratorContextManagerBase, raise RuntimeError("generator didn't stop after throw()") -class _AsyncGeneratorContextManager(_GeneratorContextManagerBase): +def _set_docstring(wrapper, wrapped): + # Issue 19330: ensure context manager instances have good docstrings + doc = getattr(wrapped, "__doc__", None) + if doc is None: + doc = type(wrapper).__doc__ + wrapper.__doc__ = doc + # Unfortunately, this still doesn't provide good help output when + # inspecting the created context manager instances, since pydoc + # currently bypasses the instance docstring and shows the docstring + # for the class instead. + # See http://bugs.python.org/issue19404 for more details. + + +class _GeneratorContextManagerDecorator(_GeneratorContextManager, + ContextDecorator): + """Helper for @contextmanager decorator.""" + + def __init__(self, func, args, kwds): + self.func, self.args, self.kwds = func, args, kwds + _set_docstring(self, func) + + def __call__(self, func): + func1, args1, kwds1 = self.func, self.args, self.kwds + @wraps(func) + def inner(*args, **kwds): + cm = _GeneratorContextManager() + cm.gen = func1(*args1, **kwds1) + with cm: + return func(*args, **kwds) + return inner + + def __enter__(self): + self.gen = self.func(*self.args, **self.kwds) + del self.func, self.args, self.kwds + return super().__enter__() + + +class _AsyncGeneratorContextManager: """Helper for @asynccontextmanager.""" + def __init__(self, func, args, kwds): + self.gen = func(*args, **kwds) + _set_docstring(self, func) + async def __aenter__(self): try: return await self.gen.__anext__() @@ -211,7 +226,7 @@ def contextmanager(func): """ @wraps(func) def helper(*args, **kwds): - return _GeneratorContextManager(func, args, kwds) + return _GeneratorContextManagerDecorator(func, args, kwds) return helper