[Python-checkins] bpo-32253: Deprecate with statement and bare await for asyncio locks (GH-4764)

Andrew Svetlov webhook-mailer at python.org
Sat Dec 9 13:00:08 EST 2017


https://github.com/python/cpython/commit/28d8d14013ade0657fed4673f5fa3c08eb2b1944
commit: 28d8d14013ade0657fed4673f5fa3c08eb2b1944
branch: master
author: Andrew Svetlov <andrew.svetlov at gmail.com>
committer: GitHub <noreply at github.com>
date: 2017-12-09T20:00:05+02:00
summary:

bpo-32253: Deprecate with statement and bare await for asyncio locks (GH-4764)

* Add test for 'with (yield from lock)'
* Deprecate with statement for asyncio locks
* Document the deprecation

files:
A Misc/NEWS.d/next/Library/2017-12-09-11-30-35.bpo-32253.TQHSYF.rst
M Doc/library/asyncio-sync.rst
M Lib/asyncio/locks.py
M Lib/test/test_asyncio/test_locks.py
M Lib/test/test_asyncio/test_pep492.py

diff --git a/Doc/library/asyncio-sync.rst b/Doc/library/asyncio-sync.rst
index 14e3defbf41..3e574f41e80 100644
--- a/Doc/library/asyncio-sync.rst
+++ b/Doc/library/asyncio-sync.rst
@@ -23,11 +23,9 @@ module (:class:`~threading.Lock`, :class:`~threading.Event`,
 :class:`~threading.BoundedSemaphore`), but it has no *timeout* parameter. The
 :func:`asyncio.wait_for` function can be used to cancel a task after a timeout.
 
-Locks
------
 
 Lock
-^^^^
+----
 
 .. class:: Lock(\*, loop=None)
 
@@ -37,8 +35,9 @@ Lock
    particular coroutine when locked.  A primitive lock is in one of two states,
    'locked' or 'unlocked'.
 
-   It is created in the unlocked state.  It has two basic methods, :meth:`acquire`
-   and :meth:`release`.  When the state is unlocked, acquire() changes the state to
+   The lock is created in the unlocked state.
+   It has two basic methods, :meth:`acquire` and :meth:`release`.
+   When the state is unlocked, acquire() changes the state to
    locked and returns immediately.  When the state is locked, acquire() blocks
    until a call to release() in another coroutine changes it to unlocked, then
    the acquire() call resets it to locked and returns.  The release() method
@@ -51,38 +50,12 @@ Lock
    resets the state to unlocked; first coroutine which is blocked in acquire()
    is being processed.
 
-   :meth:`acquire` is a coroutine and should be called with ``yield from``.
+   :meth:`acquire` is a coroutine and should be called with ``await``.
 
-   Locks also support the context management protocol.  ``(yield from lock)``
-   should be used as the context manager expression.
+   Locks support the :ref:`context management protocol <async-with-locks>`.
 
    This class is :ref:`not thread safe <asyncio-multithreading>`.
 
-   Usage::
-
-       lock = Lock()
-       ...
-       yield from lock
-       try:
-           ...
-       finally:
-           lock.release()
-
-   Context manager usage::
-
-       lock = Lock()
-       ...
-       with (yield from lock):
-           ...
-
-   Lock objects can be tested for locking state::
-
-       if not lock.locked():
-           yield from lock
-       else:
-           # lock is acquired
-           ...
-
    .. method:: locked()
 
       Return ``True`` if the lock is acquired.
@@ -110,7 +83,7 @@ Lock
 
 
 Event
-^^^^^
+-----
 
 .. class:: Event(\*, loop=None)
 
@@ -151,7 +124,7 @@ Event
 
 
 Condition
-^^^^^^^^^
+---------
 
 .. class:: Condition(lock=None, \*, loop=None)
 
@@ -166,6 +139,9 @@ Condition
    object, and it is used as the underlying lock.  Otherwise,
    a new :class:`Lock` object is created and used as the underlying lock.
 
+   Conditions support the :ref:`context management protocol
+   <async-with-locks>`.
+
    This class is :ref:`not thread safe <asyncio-multithreading>`.
 
    .. coroutinemethod:: acquire()
@@ -239,11 +215,8 @@ Condition
       This method is a :ref:`coroutine <coroutine>`.
 
 
-Semaphores
-----------
-
 Semaphore
-^^^^^^^^^
+---------
 
 .. class:: Semaphore(value=1, \*, loop=None)
 
@@ -254,12 +227,13 @@ Semaphore
    counter can never go below zero; when :meth:`acquire` finds that it is zero,
    it blocks, waiting until some other coroutine calls :meth:`release`.
 
-   Semaphores also support the context management protocol.
-
    The optional argument gives the initial value for the internal counter; it
    defaults to ``1``. If the value given is less than ``0``, :exc:`ValueError`
    is raised.
 
+   Semaphores support the :ref:`context management protocol
+   <async-with-locks>`.
+
    This class is :ref:`not thread safe <asyncio-multithreading>`.
 
    .. coroutinemethod:: acquire()
@@ -285,7 +259,7 @@ Semaphore
 
 
 BoundedSemaphore
-^^^^^^^^^^^^^^^^
+----------------
 
 .. class:: BoundedSemaphore(value=1, \*, loop=None)
 
@@ -293,3 +267,39 @@ BoundedSemaphore
 
    This raises :exc:`ValueError` in :meth:`~Semaphore.release` if it would
    increase the value above the initial value.
+
+   Bounded semapthores support the :ref:`context management
+   protocol <async-with-locks>`.
+
+   This class is :ref:`not thread safe <asyncio-multithreading>`.
+
+
+.. _async-with-locks:
+
+Using locks, conditions and semaphores in the :keyword:`async with` statement
+-----------------------------------------------------------------------------
+
+:class:`Lock`, :class:`Condition`, :class:`Semaphore`, and
+:class:`BoundedSemaphore` objects can be used in :keyword:`async with`
+statements.
+
+The :meth:`acquire` method will be called when the block is entered,
+and :meth:`release` will be called when the block is exited.  Hence,
+the following snippet::
+
+   async with lock:
+       # do something...
+
+is equivalent to::
+
+   await lock.acquire()
+   try:
+       # do something...
+   finally:
+       lock.release()
+
+.. deprecated:: 3.7
+
+   Lock acquiring using ``await lock`` or ``yield from lock`` and
+   :keyword:`with` statement (``with await lock``, ``with (yield from
+   lock)``) are deprecated.
diff --git a/Lib/asyncio/locks.py b/Lib/asyncio/locks.py
index aa6ed3eaea6..57eb69e7cf0 100644
--- a/Lib/asyncio/locks.py
+++ b/Lib/asyncio/locks.py
@@ -3,6 +3,7 @@
 __all__ = ['Lock', 'Event', 'Condition', 'Semaphore', 'BoundedSemaphore']
 
 import collections
+import warnings
 
 from . import events
 from . import futures
@@ -63,6 +64,9 @@ def __iter__(self):
         #         <block>
         #     finally:
         #         lock.release()
+        warnings.warn("'with (yield from lock)' is deprecated "
+                      "use 'async with lock' instead",
+                      DeprecationWarning, stacklevel=2)
         yield from self.acquire()
         return _ContextManager(self)
 
@@ -71,6 +75,9 @@ def __iter__(self):
         return _ContextManager(self)
 
     def __await__(self):
+        warnings.warn("'with await lock' is deprecated "
+                      "use 'async with lock' instead",
+                      DeprecationWarning, stacklevel=2)
         # To make "with await lock" work.
         return self.__acquire_ctx().__await__()
 
diff --git a/Lib/test/test_asyncio/test_locks.py b/Lib/test/test_asyncio/test_locks.py
index c1f8d6e3673..f365a45106e 100644
--- a/Lib/test/test_asyncio/test_locks.py
+++ b/Lib/test/test_asyncio/test_locks.py
@@ -42,7 +42,8 @@ def test_repr(self):
 
         @asyncio.coroutine
         def acquire_lock():
-            yield from lock
+            with self.assertWarns(DeprecationWarning):
+                yield from lock
 
         self.loop.run_until_complete(acquire_lock())
         self.assertTrue(repr(lock).endswith('[locked]>'))
@@ -53,7 +54,8 @@ def test_lock(self):
 
         @asyncio.coroutine
         def acquire_lock():
-            return (yield from lock)
+            with self.assertWarns(DeprecationWarning):
+                return (yield from lock)
 
         res = self.loop.run_until_complete(acquire_lock())
 
@@ -63,6 +65,32 @@ def acquire_lock():
         lock.release()
         self.assertFalse(lock.locked())
 
+    def test_lock_by_with_statement(self):
+        loop = asyncio.new_event_loop()  # don't use TestLoop quirks
+        self.set_event_loop(loop)
+        primitives = [
+            asyncio.Lock(loop=loop),
+            asyncio.Condition(loop=loop),
+            asyncio.Semaphore(loop=loop),
+            asyncio.BoundedSemaphore(loop=loop),
+        ]
+
+        @asyncio.coroutine
+        def test(lock):
+            yield from asyncio.sleep(0.01, loop=loop)
+            self.assertFalse(lock.locked())
+            with self.assertWarns(DeprecationWarning):
+                with (yield from lock) as _lock:
+                    self.assertIs(_lock, None)
+                    self.assertTrue(lock.locked())
+                    yield from asyncio.sleep(0.01, loop=loop)
+                    self.assertTrue(lock.locked())
+                self.assertFalse(lock.locked())
+
+        for primitive in primitives:
+            loop.run_until_complete(test(primitive))
+            self.assertFalse(primitive.locked())
+
     def test_acquire(self):
         lock = asyncio.Lock(loop=self.loop)
         result = []
@@ -212,7 +240,8 @@ def test_context_manager(self):
 
         @asyncio.coroutine
         def acquire_lock():
-            return (yield from lock)
+            with self.assertWarns(DeprecationWarning):
+                return (yield from lock)
 
         with self.loop.run_until_complete(acquire_lock()):
             self.assertTrue(lock.locked())
@@ -224,7 +253,8 @@ def test_context_manager_cant_reuse(self):
 
         @asyncio.coroutine
         def acquire_lock():
-            return (yield from lock)
+            with self.assertWarns(DeprecationWarning):
+                return (yield from lock)
 
         # This spells "yield from lock" outside a generator.
         cm = self.loop.run_until_complete(acquire_lock())
@@ -668,7 +698,8 @@ def test_context_manager(self):
 
         @asyncio.coroutine
         def acquire_cond():
-            return (yield from cond)
+            with self.assertWarns(DeprecationWarning):
+                return (yield from cond)
 
         with self.loop.run_until_complete(acquire_cond()):
             self.assertTrue(cond.locked())
@@ -751,7 +782,8 @@ def test_semaphore(self):
 
         @asyncio.coroutine
         def acquire_lock():
-            return (yield from sem)
+            with self.assertWarns(DeprecationWarning):
+                return (yield from sem)
 
         res = self.loop.run_until_complete(acquire_lock())
 
@@ -893,7 +925,8 @@ def test_context_manager(self):
 
         @asyncio.coroutine
         def acquire_lock():
-            return (yield from sem)
+            with self.assertWarns(DeprecationWarning):
+                return (yield from sem)
 
         with self.loop.run_until_complete(acquire_lock()):
             self.assertFalse(sem.locked())
diff --git a/Lib/test/test_asyncio/test_pep492.py b/Lib/test/test_asyncio/test_pep492.py
index 77eb7cd6455..442577017d9 100644
--- a/Lib/test/test_asyncio/test_pep492.py
+++ b/Lib/test/test_asyncio/test_pep492.py
@@ -59,12 +59,13 @@ def test_context_manager_with_await(self):
         async def test(lock):
             await asyncio.sleep(0.01, loop=self.loop)
             self.assertFalse(lock.locked())
-            with await lock as _lock:
-                self.assertIs(_lock, None)
-                self.assertTrue(lock.locked())
-                await asyncio.sleep(0.01, loop=self.loop)
-                self.assertTrue(lock.locked())
-            self.assertFalse(lock.locked())
+            with self.assertWarns(DeprecationWarning):
+                with await lock as _lock:
+                    self.assertIs(_lock, None)
+                    self.assertTrue(lock.locked())
+                    await asyncio.sleep(0.01, loop=self.loop)
+                    self.assertTrue(lock.locked())
+                self.assertFalse(lock.locked())
 
         for primitive in primitives:
             self.loop.run_until_complete(test(primitive))
diff --git a/Misc/NEWS.d/next/Library/2017-12-09-11-30-35.bpo-32253.TQHSYF.rst b/Misc/NEWS.d/next/Library/2017-12-09-11-30-35.bpo-32253.TQHSYF.rst
new file mode 100644
index 00000000000..2916410e457
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2017-12-09-11-30-35.bpo-32253.TQHSYF.rst
@@ -0,0 +1,2 @@
+Deprecate ``yield from lock``, ``await lock``, ``with (yield from lock)``
+and ``with await lock`` for asyncio synchronization primitives.



More information about the Python-checkins mailing list