mirror of
https://github.com/python/cpython.git
synced 2025-08-04 08:59:19 +00:00
Issue #25609: Introduce contextlib.AbstractContextManager and
typing.ContextManager.
This commit is contained in:
parent
c5b5ba9bda
commit
9e080e0e74
8 changed files with 138 additions and 19 deletions
|
@ -18,6 +18,18 @@ Utilities
|
||||||
|
|
||||||
Functions and classes provided:
|
Functions and classes provided:
|
||||||
|
|
||||||
|
.. class:: AbstractContextManager
|
||||||
|
|
||||||
|
An abstract base class for classes that implement
|
||||||
|
:meth:`object.__enter__` and :meth:`object.__exit__`. A default
|
||||||
|
implementation for :meth:`object.__enter__` is provided which returns
|
||||||
|
``self`` while :meth:`object.__exit__` is an abstract method which by default
|
||||||
|
returns ``None``. See also the definition of :ref:`typecontextmanager`.
|
||||||
|
|
||||||
|
.. versionadded:: 3.6
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. decorator:: contextmanager
|
.. decorator:: contextmanager
|
||||||
|
|
||||||
This function is a :term:`decorator` that can be used to define a factory
|
This function is a :term:`decorator` that can be used to define a factory
|
||||||
|
@ -447,9 +459,9 @@ Here's an example of doing this for a context manager that accepts resource
|
||||||
acquisition and release functions, along with an optional validation function,
|
acquisition and release functions, along with an optional validation function,
|
||||||
and maps them to the context management protocol::
|
and maps them to the context management protocol::
|
||||||
|
|
||||||
from contextlib import contextmanager, ExitStack
|
from contextlib import contextmanager, AbstractContextManager, ExitStack
|
||||||
|
|
||||||
class ResourceManager:
|
class ResourceManager(AbstractContextManager):
|
||||||
|
|
||||||
def __init__(self, acquire_resource, release_resource, check_resource_ok=None):
|
def __init__(self, acquire_resource, release_resource, check_resource_ok=None):
|
||||||
self.acquire_resource = acquire_resource
|
self.acquire_resource = acquire_resource
|
||||||
|
|
|
@ -345,15 +345,15 @@ The module defines the following classes, functions and decorators:
|
||||||
|
|
||||||
.. class:: Iterable(Generic[T_co])
|
.. class:: Iterable(Generic[T_co])
|
||||||
|
|
||||||
A generic version of the :class:`collections.abc.Iterable`.
|
A generic version of :class:`collections.abc.Iterable`.
|
||||||
|
|
||||||
.. class:: Iterator(Iterable[T_co])
|
.. class:: Iterator(Iterable[T_co])
|
||||||
|
|
||||||
A generic version of the :class:`collections.abc.Iterator`.
|
A generic version of :class:`collections.abc.Iterator`.
|
||||||
|
|
||||||
.. class:: Reversible(Iterable[T_co])
|
.. class:: Reversible(Iterable[T_co])
|
||||||
|
|
||||||
A generic version of the :class:`collections.abc.Reversible`.
|
A generic version of :class:`collections.abc.Reversible`.
|
||||||
|
|
||||||
.. class:: SupportsInt
|
.. class:: SupportsInt
|
||||||
|
|
||||||
|
@ -448,6 +448,12 @@ The module defines the following classes, functions and decorators:
|
||||||
|
|
||||||
A generic version of :class:`collections.abc.ValuesView`.
|
A generic version of :class:`collections.abc.ValuesView`.
|
||||||
|
|
||||||
|
.. class:: ContextManager(Generic[T_co])
|
||||||
|
|
||||||
|
A generic version of :class:`contextlib.AbstractContextManager`.
|
||||||
|
|
||||||
|
.. versionadded:: 3.6
|
||||||
|
|
||||||
.. class:: Dict(dict, MutableMapping[KT, VT])
|
.. class:: Dict(dict, MutableMapping[KT, VT])
|
||||||
|
|
||||||
A generic version of :class:`dict`.
|
A generic version of :class:`dict`.
|
||||||
|
|
|
@ -190,6 +190,18 @@ New Modules
|
||||||
Improved Modules
|
Improved Modules
|
||||||
================
|
================
|
||||||
|
|
||||||
|
contextlib
|
||||||
|
----------
|
||||||
|
|
||||||
|
The :class:`contextlib.AbstractContextManager` class has been added to
|
||||||
|
provide an abstract base class for context managers. It provides a
|
||||||
|
sensible default implementation for `__enter__()` which returns
|
||||||
|
`self` and leaves `__exit__()` an abstract method. A matching
|
||||||
|
class has been added to the :mod:`typing` module as
|
||||||
|
:class:`typing.ContextManager`.
|
||||||
|
(Contributed by Brett Cannon in :issue:`25609`.)
|
||||||
|
|
||||||
|
|
||||||
datetime
|
datetime
|
||||||
--------
|
--------
|
||||||
|
|
||||||
|
@ -246,6 +258,14 @@ telnetlib
|
||||||
Stéphane Wirtel in :issue:`25485`).
|
Stéphane Wirtel in :issue:`25485`).
|
||||||
|
|
||||||
|
|
||||||
|
typing
|
||||||
|
------
|
||||||
|
|
||||||
|
The :class:`typing.ContextManager` class has been added for
|
||||||
|
representing :class:`contextlib.AbstractContextManager`.
|
||||||
|
(Contributed by Brett Cannon in :issue:`25609`.)
|
||||||
|
|
||||||
|
|
||||||
unittest.mock
|
unittest.mock
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
|
@ -372,9 +392,9 @@ become proper keywords in Python 3.7.
|
||||||
Deprecated Python modules, functions and methods
|
Deprecated Python modules, functions and methods
|
||||||
------------------------------------------------
|
------------------------------------------------
|
||||||
|
|
||||||
* :meth:`importlib.machinery.SourceFileLoader` and
|
* :meth:`importlib.machinery.SourceFileLoader.load_module` and
|
||||||
:meth:`importlib.machinery.SourcelessFileLoader` are now deprecated. They
|
:meth:`importlib.machinery.SourcelessFileLoader.load_module` are now
|
||||||
were the only remaining implementations of
|
deprecated. They were the only remaining implementations of
|
||||||
:meth:`importlib.abc.Loader.load_module` in :mod:`importlib` that had not
|
:meth:`importlib.abc.Loader.load_module` in :mod:`importlib` that had not
|
||||||
been deprecated in previous versions of Python in favour of
|
been deprecated in previous versions of Python in favour of
|
||||||
:meth:`importlib.abc.Loader.exec_module`.
|
:meth:`importlib.abc.Loader.exec_module`.
|
||||||
|
|
|
@ -1,11 +1,34 @@
|
||||||
"""Utilities for with-statement contexts. See PEP 343."""
|
"""Utilities for with-statement contexts. See PEP 343."""
|
||||||
|
import abc
|
||||||
import sys
|
import sys
|
||||||
from collections import deque
|
from collections import deque
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
|
||||||
__all__ = ["contextmanager", "closing", "ContextDecorator", "ExitStack",
|
__all__ = ["contextmanager", "closing", "AbstractContextManager",
|
||||||
"redirect_stdout", "redirect_stderr", "suppress"]
|
"ContextDecorator", "ExitStack", "redirect_stdout",
|
||||||
|
"redirect_stderr", "suppress"]
|
||||||
|
|
||||||
|
|
||||||
|
class AbstractContextManager(abc.ABC):
|
||||||
|
|
||||||
|
"""An abstract base class for context managers."""
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
"""Return `self` upon entering the runtime context."""
|
||||||
|
return self
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def __exit__(self, exc_type, exc_value, traceback):
|
||||||
|
"""Raise any exception triggered within the runtime context."""
|
||||||
|
return None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def __subclasshook__(cls, C):
|
||||||
|
if cls is AbstractContextManager:
|
||||||
|
if (any("__enter__" in B.__dict__ for B in C.__mro__) and
|
||||||
|
any("__exit__" in B.__dict__ for B in C.__mro__)):
|
||||||
|
return True
|
||||||
|
return NotImplemented
|
||||||
|
|
||||||
|
|
||||||
class ContextDecorator(object):
|
class ContextDecorator(object):
|
||||||
|
@ -31,7 +54,7 @@ class ContextDecorator(object):
|
||||||
return inner
|
return inner
|
||||||
|
|
||||||
|
|
||||||
class _GeneratorContextManager(ContextDecorator):
|
class _GeneratorContextManager(ContextDecorator, AbstractContextManager):
|
||||||
"""Helper for @contextmanager decorator."""
|
"""Helper for @contextmanager decorator."""
|
||||||
|
|
||||||
def __init__(self, func, args, kwds):
|
def __init__(self, func, args, kwds):
|
||||||
|
@ -134,7 +157,7 @@ def contextmanager(func):
|
||||||
return helper
|
return helper
|
||||||
|
|
||||||
|
|
||||||
class closing(object):
|
class closing(AbstractContextManager):
|
||||||
"""Context to automatically close something at the end of a block.
|
"""Context to automatically close something at the end of a block.
|
||||||
|
|
||||||
Code like this:
|
Code like this:
|
||||||
|
@ -159,7 +182,7 @@ class closing(object):
|
||||||
self.thing.close()
|
self.thing.close()
|
||||||
|
|
||||||
|
|
||||||
class _RedirectStream:
|
class _RedirectStream(AbstractContextManager):
|
||||||
|
|
||||||
_stream = None
|
_stream = None
|
||||||
|
|
||||||
|
@ -199,7 +222,7 @@ class redirect_stderr(_RedirectStream):
|
||||||
_stream = "stderr"
|
_stream = "stderr"
|
||||||
|
|
||||||
|
|
||||||
class suppress:
|
class suppress(AbstractContextManager):
|
||||||
"""Context manager to suppress specified exceptions
|
"""Context manager to suppress specified exceptions
|
||||||
|
|
||||||
After the exception is suppressed, execution proceeds with the next
|
After the exception is suppressed, execution proceeds with the next
|
||||||
|
@ -230,7 +253,7 @@ class suppress:
|
||||||
|
|
||||||
|
|
||||||
# Inspired by discussions on http://bugs.python.org/issue13585
|
# Inspired by discussions on http://bugs.python.org/issue13585
|
||||||
class ExitStack(object):
|
class ExitStack(AbstractContextManager):
|
||||||
"""Context manager for dynamic management of a stack of exit callbacks
|
"""Context manager for dynamic management of a stack of exit callbacks
|
||||||
|
|
||||||
For example:
|
For example:
|
||||||
|
@ -309,9 +332,6 @@ class ExitStack(object):
|
||||||
"""Immediately unwind the context stack"""
|
"""Immediately unwind the context stack"""
|
||||||
self.__exit__(None, None, None)
|
self.__exit__(None, None, None)
|
||||||
|
|
||||||
def __enter__(self):
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __exit__(self, *exc_details):
|
def __exit__(self, *exc_details):
|
||||||
received_exc = exc_details[0] is not None
|
received_exc = exc_details[0] is not None
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,39 @@ except ImportError:
|
||||||
threading = None
|
threading = None
|
||||||
|
|
||||||
|
|
||||||
|
class TestAbstractContextManager(unittest.TestCase):
|
||||||
|
|
||||||
|
def test_enter(self):
|
||||||
|
class DefaultEnter(AbstractContextManager):
|
||||||
|
def __exit__(self, *args):
|
||||||
|
super().__exit__(*args)
|
||||||
|
|
||||||
|
manager = DefaultEnter()
|
||||||
|
self.assertIs(manager.__enter__(), manager)
|
||||||
|
|
||||||
|
def test_exit_is_abstract(self):
|
||||||
|
class MissingExit(AbstractContextManager):
|
||||||
|
pass
|
||||||
|
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
MissingExit()
|
||||||
|
|
||||||
|
def test_structural_subclassing(self):
|
||||||
|
class ManagerFromScratch:
|
||||||
|
def __enter__(self):
|
||||||
|
return self
|
||||||
|
def __exit__(self, exc_type, exc_value, traceback):
|
||||||
|
return None
|
||||||
|
|
||||||
|
self.assertTrue(issubclass(ManagerFromScratch, AbstractContextManager))
|
||||||
|
|
||||||
|
class DefaultEnter(AbstractContextManager):
|
||||||
|
def __exit__(self, *args):
|
||||||
|
super().__exit__(*args)
|
||||||
|
|
||||||
|
self.assertTrue(issubclass(DefaultEnter, AbstractContextManager))
|
||||||
|
|
||||||
|
|
||||||
class ContextManagerTestCase(unittest.TestCase):
|
class ContextManagerTestCase(unittest.TestCase):
|
||||||
|
|
||||||
def test_contextmanager_plain(self):
|
def test_contextmanager_plain(self):
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import contextlib
|
||||||
import pickle
|
import pickle
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
@ -1309,6 +1310,21 @@ class CollectionsAbcTests(TestCase):
|
||||||
assert len(MMB[KT, VT]()) == 0
|
assert len(MMB[KT, VT]()) == 0
|
||||||
|
|
||||||
|
|
||||||
|
class OtherABCTests(TestCase):
|
||||||
|
|
||||||
|
@skipUnless(hasattr(typing, 'ContextManager'),
|
||||||
|
'requires typing.ContextManager')
|
||||||
|
def test_contextmanager(self):
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def manager():
|
||||||
|
yield 42
|
||||||
|
|
||||||
|
cm = manager()
|
||||||
|
assert isinstance(cm, typing.ContextManager)
|
||||||
|
assert isinstance(cm, typing.ContextManager[int])
|
||||||
|
assert not isinstance(42, typing.ContextManager)
|
||||||
|
|
||||||
|
|
||||||
class NamedTupleTests(TestCase):
|
class NamedTupleTests(TestCase):
|
||||||
|
|
||||||
def test_basics(self):
|
def test_basics(self):
|
||||||
|
@ -1447,6 +1463,8 @@ class AllTests(TestCase):
|
||||||
assert 'ValuesView' in a
|
assert 'ValuesView' in a
|
||||||
assert 'cast' in a
|
assert 'cast' in a
|
||||||
assert 'overload' in a
|
assert 'overload' in a
|
||||||
|
if hasattr(contextlib, 'AbstractContextManager'):
|
||||||
|
assert 'ContextManager' in a
|
||||||
# Check that io and re are not exported.
|
# Check that io and re are not exported.
|
||||||
assert 'io' not in a
|
assert 'io' not in a
|
||||||
assert 're' not in a
|
assert 're' not in a
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import abc
|
import abc
|
||||||
from abc import abstractmethod, abstractproperty
|
from abc import abstractmethod, abstractproperty
|
||||||
import collections
|
import collections
|
||||||
|
import contextlib
|
||||||
import functools
|
import functools
|
||||||
import re as stdlib_re # Avoid confusion with the re we export.
|
import re as stdlib_re # Avoid confusion with the re we export.
|
||||||
import sys
|
import sys
|
||||||
|
@ -1530,6 +1531,12 @@ class ValuesView(MappingView[VT_co], extra=collections_abc.ValuesView):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
if hasattr(contextlib, 'AbstractContextManager'):
|
||||||
|
class ContextManager(Generic[T_co], extra=contextlib.AbstractContextManager):
|
||||||
|
__slots__ = ()
|
||||||
|
__all__.append('ContextManager')
|
||||||
|
|
||||||
|
|
||||||
class Dict(dict, MutableMapping[KT, VT]):
|
class Dict(dict, MutableMapping[KT, VT]):
|
||||||
|
|
||||||
def __new__(cls, *args, **kwds):
|
def __new__(cls, *args, **kwds):
|
||||||
|
|
|
@ -237,6 +237,9 @@ Core and Builtins
|
||||||
Library
|
Library
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
- Issue #25609: Introduce contextlib.AbstractContextManager and
|
||||||
|
typing.ContextManager.
|
||||||
|
|
||||||
- Issue #26709: Fixed Y2038 problem in loading binary PLists.
|
- Issue #26709: Fixed Y2038 problem in loading binary PLists.
|
||||||
|
|
||||||
- Issue #23735: Handle terminal resizing with Readline 6.3+ by installing our
|
- Issue #23735: Handle terminal resizing with Readline 6.3+ by installing our
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue