Skip to content

Commit 28d8d14

Browse files
authored
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
1 parent a9f8df6 commit 28d8d14

File tree

5 files changed

+108
-55
lines changed

5 files changed

+108
-55
lines changed

Doc/library/asyncio-sync.rst

Lines changed: 52 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,9 @@ module (:class:`~threading.Lock`, :class:`~threading.Event`,
2323
:class:`~threading.BoundedSemaphore`), but it has no *timeout* parameter. The
2424
:func:`asyncio.wait_for` function can be used to cancel a task after a timeout.
2525

26-
Locks
27-
-----
2826

2927
Lock
30-
^^^^
28+
----
3129

3230
.. class:: Lock(\*, loop=None)
3331

@@ -37,8 +35,9 @@ Lock
3735
particular coroutine when locked. A primitive lock is in one of two states,
3836
'locked' or 'unlocked'.
3937

40-
It is created in the unlocked state. It has two basic methods, :meth:`acquire`
41-
and :meth:`release`. When the state is unlocked, acquire() changes the state to
38+
The lock is created in the unlocked state.
39+
It has two basic methods, :meth:`acquire` and :meth:`release`.
40+
When the state is unlocked, acquire() changes the state to
4241
locked and returns immediately. When the state is locked, acquire() blocks
4342
until a call to release() in another coroutine changes it to unlocked, then
4443
the acquire() call resets it to locked and returns. The release() method
@@ -51,38 +50,12 @@ Lock
5150
resets the state to unlocked; first coroutine which is blocked in acquire()
5251
is being processed.
5352

54-
:meth:`acquire` is a coroutine and should be called with ``yield from``.
53+
:meth:`acquire` is a coroutine and should be called with ``await``.
5554

56-
Locks also support the context management protocol. ``(yield from lock)``
57-
should be used as the context manager expression.
55+
Locks support the :ref:`context management protocol <async-with-locks>`.
5856

5957
This class is :ref:`not thread safe <asyncio-multithreading>`.
6058

61-
Usage::
62-
63-
lock = Lock()
64-
...
65-
yield from lock
66-
try:
67-
...
68-
finally:
69-
lock.release()
70-
71-
Context manager usage::
72-
73-
lock = Lock()
74-
...
75-
with (yield from lock):
76-
...
77-
78-
Lock objects can be tested for locking state::
79-
80-
if not lock.locked():
81-
yield from lock
82-
else:
83-
# lock is acquired
84-
...
85-
8659
.. method:: locked()
8760

8861
Return ``True`` if the lock is acquired.
@@ -110,7 +83,7 @@ Lock
11083

11184

11285
Event
113-
^^^^^
86+
-----
11487

11588
.. class:: Event(\*, loop=None)
11689

@@ -151,7 +124,7 @@ Event
151124

152125

153126
Condition
154-
^^^^^^^^^
127+
---------
155128

156129
.. class:: Condition(lock=None, \*, loop=None)
157130

@@ -166,6 +139,9 @@ Condition
166139
object, and it is used as the underlying lock. Otherwise,
167140
a new :class:`Lock` object is created and used as the underlying lock.
168141

142+
Conditions support the :ref:`context management protocol
143+
<async-with-locks>`.
144+
169145
This class is :ref:`not thread safe <asyncio-multithreading>`.
170146

171147
.. coroutinemethod:: acquire()
@@ -239,11 +215,8 @@ Condition
239215
This method is a :ref:`coroutine <coroutine>`.
240216

241217

242-
Semaphores
243-
----------
244-
245218
Semaphore
246-
^^^^^^^^^
219+
---------
247220

248221
.. class:: Semaphore(value=1, \*, loop=None)
249222

@@ -254,12 +227,13 @@ Semaphore
254227
counter can never go below zero; when :meth:`acquire` finds that it is zero,
255228
it blocks, waiting until some other coroutine calls :meth:`release`.
256229

257-
Semaphores also support the context management protocol.
258-
259230
The optional argument gives the initial value for the internal counter; it
260231
defaults to ``1``. If the value given is less than ``0``, :exc:`ValueError`
261232
is raised.
262233

234+
Semaphores support the :ref:`context management protocol
235+
<async-with-locks>`.
236+
263237
This class is :ref:`not thread safe <asyncio-multithreading>`.
264238

265239
.. coroutinemethod:: acquire()
@@ -285,11 +259,47 @@ Semaphore
285259

286260

287261
BoundedSemaphore
288-
^^^^^^^^^^^^^^^^
262+
----------------
289263

290264
.. class:: BoundedSemaphore(value=1, \*, loop=None)
291265

292266
A bounded semaphore implementation. Inherit from :class:`Semaphore`.
293267

294268
This raises :exc:`ValueError` in :meth:`~Semaphore.release` if it would
295269
increase the value above the initial value.
270+
271+
Bounded semapthores support the :ref:`context management
272+
protocol <async-with-locks>`.
273+
274+
This class is :ref:`not thread safe <asyncio-multithreading>`.
275+
276+
277+
.. _async-with-locks:
278+
279+
Using locks, conditions and semaphores in the :keyword:`async with` statement
280+
-----------------------------------------------------------------------------
281+
282+
:class:`Lock`, :class:`Condition`, :class:`Semaphore`, and
283+
:class:`BoundedSemaphore` objects can be used in :keyword:`async with`
284+
statements.
285+
286+
The :meth:`acquire` method will be called when the block is entered,
287+
and :meth:`release` will be called when the block is exited. Hence,
288+
the following snippet::
289+
290+
async with lock:
291+
# do something...
292+
293+
is equivalent to::
294+
295+
await lock.acquire()
296+
try:
297+
# do something...
298+
finally:
299+
lock.release()
300+
301+
.. deprecated:: 3.7
302+
303+
Lock acquiring using ``await lock`` or ``yield from lock`` and
304+
:keyword:`with` statement (``with await lock``, ``with (yield from
305+
lock)``) are deprecated.

Lib/asyncio/locks.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
__all__ = ['Lock', 'Event', 'Condition', 'Semaphore', 'BoundedSemaphore']
44

55
import collections
6+
import warnings
67

78
from . import events
89
from . import futures
@@ -63,6 +64,9 @@ def __iter__(self):
6364
# <block>
6465
# finally:
6566
# lock.release()
67+
warnings.warn("'with (yield from lock)' is deprecated "
68+
"use 'async with lock' instead",
69+
DeprecationWarning, stacklevel=2)
6670
yield from self.acquire()
6771
return _ContextManager(self)
6872

@@ -71,6 +75,9 @@ async def __acquire_ctx(self):
7175
return _ContextManager(self)
7276

7377
def __await__(self):
78+
warnings.warn("'with await lock' is deprecated "
79+
"use 'async with lock' instead",
80+
DeprecationWarning, stacklevel=2)
7481
# To make "with await lock" work.
7582
return self.__acquire_ctx().__await__()
7683

Lib/test/test_asyncio/test_locks.py

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ def test_repr(self):
4242

4343
@asyncio.coroutine
4444
def acquire_lock():
45-
yield from lock
45+
with self.assertWarns(DeprecationWarning):
46+
yield from lock
4647

4748
self.loop.run_until_complete(acquire_lock())
4849
self.assertTrue(repr(lock).endswith('[locked]>'))
@@ -53,7 +54,8 @@ def test_lock(self):
5354

5455
@asyncio.coroutine
5556
def acquire_lock():
56-
return (yield from lock)
57+
with self.assertWarns(DeprecationWarning):
58+
return (yield from lock)
5759

5860
res = self.loop.run_until_complete(acquire_lock())
5961

@@ -63,6 +65,32 @@ def acquire_lock():
6365
lock.release()
6466
self.assertFalse(lock.locked())
6567

68+
def test_lock_by_with_statement(self):
69+
loop = asyncio.new_event_loop() # don't use TestLoop quirks
70+
self.set_event_loop(loop)
71+
primitives = [
72+
asyncio.Lock(loop=loop),
73+
asyncio.Condition(loop=loop),
74+
asyncio.Semaphore(loop=loop),
75+
asyncio.BoundedSemaphore(loop=loop),
76+
]
77+
78+
@asyncio.coroutine
79+
def test(lock):
80+
yield from asyncio.sleep(0.01, loop=loop)
81+
self.assertFalse(lock.locked())
82+
with self.assertWarns(DeprecationWarning):
83+
with (yield from lock) as _lock:
84+
self.assertIs(_lock, None)
85+
self.assertTrue(lock.locked())
86+
yield from asyncio.sleep(0.01, loop=loop)
87+
self.assertTrue(lock.locked())
88+
self.assertFalse(lock.locked())
89+
90+
for primitive in primitives:
91+
loop.run_until_complete(test(primitive))
92+
self.assertFalse(primitive.locked())
93+
6694
def test_acquire(self):
6795
lock = asyncio.Lock(loop=self.loop)
6896
result = []
@@ -212,7 +240,8 @@ def test_context_manager(self):
212240

213241
@asyncio.coroutine
214242
def acquire_lock():
215-
return (yield from lock)
243+
with self.assertWarns(DeprecationWarning):
244+
return (yield from lock)
216245

217246
with self.loop.run_until_complete(acquire_lock()):
218247
self.assertTrue(lock.locked())
@@ -224,7 +253,8 @@ def test_context_manager_cant_reuse(self):
224253

225254
@asyncio.coroutine
226255
def acquire_lock():
227-
return (yield from lock)
256+
with self.assertWarns(DeprecationWarning):
257+
return (yield from lock)
228258

229259
# This spells "yield from lock" outside a generator.
230260
cm = self.loop.run_until_complete(acquire_lock())
@@ -668,7 +698,8 @@ def test_context_manager(self):
668698

669699
@asyncio.coroutine
670700
def acquire_cond():
671-
return (yield from cond)
701+
with self.assertWarns(DeprecationWarning):
702+
return (yield from cond)
672703

673704
with self.loop.run_until_complete(acquire_cond()):
674705
self.assertTrue(cond.locked())
@@ -751,7 +782,8 @@ def test_semaphore(self):
751782

752783
@asyncio.coroutine
753784
def acquire_lock():
754-
return (yield from sem)
785+
with self.assertWarns(DeprecationWarning):
786+
return (yield from sem)
755787

756788
res = self.loop.run_until_complete(acquire_lock())
757789

@@ -893,7 +925,8 @@ def test_context_manager(self):
893925

894926
@asyncio.coroutine
895927
def acquire_lock():
896-
return (yield from sem)
928+
with self.assertWarns(DeprecationWarning):
929+
return (yield from sem)
897930

898931
with self.loop.run_until_complete(acquire_lock()):
899932
self.assertFalse(sem.locked())

Lib/test/test_asyncio/test_pep492.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,13 @@ def test_context_manager_with_await(self):
5959
async def test(lock):
6060
await asyncio.sleep(0.01, loop=self.loop)
6161
self.assertFalse(lock.locked())
62-
with await lock as _lock:
63-
self.assertIs(_lock, None)
64-
self.assertTrue(lock.locked())
65-
await asyncio.sleep(0.01, loop=self.loop)
66-
self.assertTrue(lock.locked())
67-
self.assertFalse(lock.locked())
62+
with self.assertWarns(DeprecationWarning):
63+
with await lock as _lock:
64+
self.assertIs(_lock, None)
65+
self.assertTrue(lock.locked())
66+
await asyncio.sleep(0.01, loop=self.loop)
67+
self.assertTrue(lock.locked())
68+
self.assertFalse(lock.locked())
6869

6970
for primitive in primitives:
7071
self.loop.run_until_complete(test(primitive))
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Deprecate ``yield from lock``, ``await lock``, ``with (yield from lock)``
2+
and ``with await lock`` for asyncio synchronization primitives.

0 commit comments

Comments
 (0)