Merge
This commit is contained in:
commit
61d85bab4d
|
@ -218,30 +218,32 @@ Thread Objects
|
|||
|
||||
This class represents an activity that is run in a separate thread of control.
|
||||
There are two ways to specify the activity: by passing a callable object to the
|
||||
constructor, or by overriding the :meth:`run` method in a subclass. No other
|
||||
methods (except for the constructor) should be overridden in a subclass. In
|
||||
other words, *only* override the :meth:`__init__` and :meth:`run` methods of
|
||||
this class.
|
||||
constructor, or by overriding the :meth:`~Thread.run` method in a subclass.
|
||||
No other methods (except for the constructor) should be overridden in a
|
||||
subclass. In other words, *only* override the :meth:`~Thread.__init__`
|
||||
and :meth:`~Thread.run` methods of this class.
|
||||
|
||||
Once a thread object is created, its activity must be started by calling the
|
||||
thread's :meth:`start` method. This invokes the :meth:`run` method in a
|
||||
separate thread of control.
|
||||
thread's :meth:`~Thread.start` method. This invokes the :meth:`~Thread.run`
|
||||
method in a separate thread of control.
|
||||
|
||||
Once the thread's activity is started, the thread is considered 'alive'. It
|
||||
stops being alive when its :meth:`run` method terminates -- either normally, or
|
||||
by raising an unhandled exception. The :meth:`is_alive` method tests whether the
|
||||
thread is alive.
|
||||
stops being alive when its :meth:`~Thread.run` method terminates -- either
|
||||
normally, or by raising an unhandled exception. The :meth:`~Thread.is_alive`
|
||||
method tests whether the thread is alive.
|
||||
|
||||
Other threads can call a thread's :meth:`join` method. This blocks the calling
|
||||
thread until the thread whose :meth:`join` method is called is terminated.
|
||||
Other threads can call a thread's :meth:`~Thread.join` method. This blocks
|
||||
the calling thread until the thread whose :meth:`~Thread.join` method is
|
||||
called is terminated.
|
||||
|
||||
A thread has a name. The name can be passed to the constructor, and read or
|
||||
changed through the :attr:`name` attribute.
|
||||
changed through the :attr:`~Thread.name` attribute.
|
||||
|
||||
A thread can be flagged as a "daemon thread". The significance of this flag is
|
||||
that the entire Python program exits when only daemon threads are left. The
|
||||
initial value is inherited from the creating thread. The flag can be set
|
||||
through the :attr:`daemon` property or the *daemon* constructor argument.
|
||||
through the :attr:`~Thread.daemon` property or the *daemon* constructor
|
||||
argument.
|
||||
|
||||
There is a "main thread" object; this corresponds to the initial thread of
|
||||
control in the Python program. It is not a daemon thread.
|
||||
|
@ -250,8 +252,8 @@ There is the possibility that "dummy thread objects" are created. These are
|
|||
thread objects corresponding to "alien threads", which are threads of control
|
||||
started outside the threading module, such as directly from C code. Dummy
|
||||
thread objects have limited functionality; they are always considered alive and
|
||||
daemonic, and cannot be :meth:`join`\ ed. They are never deleted, since it is
|
||||
impossible to detect the termination of alien threads.
|
||||
daemonic, and cannot be :meth:`~Thread.join`\ ed. They are never deleted,
|
||||
since it is impossible to detect the termination of alien threads.
|
||||
|
||||
|
||||
.. class:: Thread(group=None, target=None, name=None, args=(), kwargs={},
|
||||
|
@ -292,7 +294,8 @@ impossible to detect the termination of alien threads.
|
|||
Start the thread's activity.
|
||||
|
||||
It must be called at most once per thread object. It arranges for the
|
||||
object's :meth:`run` method to be invoked in a separate thread of control.
|
||||
object's :meth:`~Thread.run` method to be invoked in a separate thread
|
||||
of control.
|
||||
|
||||
This method will raise a :exc:`RuntimeError` if called more than once
|
||||
on the same thread object.
|
||||
|
@ -308,25 +311,27 @@ impossible to detect the termination of alien threads.
|
|||
|
||||
.. method:: join(timeout=None)
|
||||
|
||||
Wait until the thread terminates. This blocks the calling thread until the
|
||||
thread whose :meth:`join` method is called terminates -- either normally
|
||||
or through an unhandled exception -- or until the optional timeout occurs.
|
||||
Wait until the thread terminates. This blocks the calling thread until
|
||||
the thread whose :meth:`~Thread.join` method is called terminates -- either
|
||||
normally or through an unhandled exception --, or until the optional
|
||||
timeout occurs.
|
||||
|
||||
When the *timeout* argument is present and not ``None``, it should be a
|
||||
floating point number specifying a timeout for the operation in seconds
|
||||
(or fractions thereof). As :meth:`join` always returns ``None``, you must
|
||||
call :meth:`is_alive` after :meth:`join` to decide whether a timeout
|
||||
happened -- if the thread is still alive, the :meth:`join` call timed out.
|
||||
(or fractions thereof). As :meth:`~Thread.join` always returns ``None``,
|
||||
you must call :meth:`~Thread.is_alive` after :meth:`~Thread.join` to
|
||||
decide whether a timeout happened -- if the thread is still alive, the
|
||||
:meth:`~Thread.join` call timed out.
|
||||
|
||||
When the *timeout* argument is not present or ``None``, the operation will
|
||||
block until the thread terminates.
|
||||
|
||||
A thread can be :meth:`join`\ ed many times.
|
||||
A thread can be :meth:`~Thread.join`\ ed many times.
|
||||
|
||||
:meth:`join` raises a :exc:`RuntimeError` if an attempt is made to join
|
||||
the current thread as that would cause a deadlock. It is also an error to
|
||||
:meth:`join` a thread before it has been started and attempts to do so
|
||||
raises the same exception.
|
||||
:meth:`~Thread.join` raises a :exc:`RuntimeError` if an attempt is made
|
||||
to join the current thread as that would cause a deadlock. It is also
|
||||
an error to :meth:`~Thread.join` a thread before it has been started
|
||||
and attempts to do so raise the same exception.
|
||||
|
||||
.. attribute:: name
|
||||
|
||||
|
@ -343,27 +348,27 @@ impossible to detect the termination of alien threads.
|
|||
.. attribute:: ident
|
||||
|
||||
The 'thread identifier' of this thread or ``None`` if the thread has not
|
||||
been started. This is a nonzero integer. See the :func:`get_ident()`
|
||||
function. Thread identifiers may be recycled when a thread exits and
|
||||
another thread is created. The identifier is available even after the
|
||||
thread has exited.
|
||||
been started. This is a nonzero integer. See the
|
||||
:func:`_thread.get_ident()` function. Thread identifiers may be recycled
|
||||
when a thread exits and another thread is created. The identifier is
|
||||
available even after the thread has exited.
|
||||
|
||||
.. method:: is_alive()
|
||||
|
||||
Return whether the thread is alive.
|
||||
|
||||
This method returns ``True`` just before the :meth:`run` method starts
|
||||
until just after the :meth:`run` method terminates. The module function
|
||||
:func:`.enumerate` returns a list of all alive threads.
|
||||
This method returns ``True`` just before the :meth:`~Thread.run` method
|
||||
starts until just after the :meth:`~Thread.run` method terminates. The
|
||||
module function :func:`.enumerate` returns a list of all alive threads.
|
||||
|
||||
.. attribute:: daemon
|
||||
|
||||
A boolean value indicating whether this thread is a daemon thread (True)
|
||||
or not (False). This must be set before :meth:`start` is called,
|
||||
or not (False). This must be set before :meth:`~Thread.start` is called,
|
||||
otherwise :exc:`RuntimeError` is raised. Its initial value is inherited
|
||||
from the creating thread; the main thread is not a daemon thread and
|
||||
therefore all threads created in the main thread default to :attr:`daemon`
|
||||
= ``False``.
|
||||
therefore all threads created in the main thread default to
|
||||
:attr:`~Thread.daemon` = ``False``.
|
||||
|
||||
The entire Python program exits when no alive non-daemon threads are left.
|
||||
|
||||
|
@ -397,19 +402,22 @@ synchronization primitive available, implemented directly by the :mod:`_thread`
|
|||
extension module.
|
||||
|
||||
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, :meth:`acquire` changes the state
|
||||
to locked and returns immediately. When the state is locked, :meth:`acquire`
|
||||
blocks until a call to :meth:`release` in another thread changes it to unlocked,
|
||||
then the :meth:`acquire` call resets it to locked and returns. The
|
||||
:meth:`release` method should only be called in the locked state; it changes the
|
||||
state to unlocked and returns immediately. If an attempt is made to release an
|
||||
unlocked lock, a :exc:`RuntimeError` will be raised.
|
||||
in the unlocked state. It has two basic methods, :meth:`~Lock.acquire` and
|
||||
:meth:`~Lock.release`. When the state is unlocked, :meth:`~Lock.acquire`
|
||||
changes the state to locked and returns immediately. When the state is locked,
|
||||
:meth:`~Lock.acquire` blocks until a call to :meth:`~Lock.release` in another
|
||||
thread changes it to unlocked, then the :meth:`~Lock.acquire` call resets it
|
||||
to locked and returns. The :meth:`~Lock.release` method should only be
|
||||
called in the locked state; it changes the state to unlocked and returns
|
||||
immediately. If an attempt is made to release an unlocked lock, a
|
||||
:exc:`RuntimeError` will be raised.
|
||||
|
||||
When more than one thread is blocked in :meth:`acquire` waiting for the state to
|
||||
turn to unlocked, only one thread proceeds when a :meth:`release` call resets
|
||||
the state to unlocked; which one of the waiting threads proceeds is not defined,
|
||||
and may vary across implementations.
|
||||
Locks also support the :ref:`context manager protocol <with-locks>`.
|
||||
|
||||
When more than one thread is blocked in :meth:`~Lock.acquire` waiting for the
|
||||
state to turn to unlocked, only one thread proceeds when a :meth:`~Lock.release`
|
||||
call resets the state to unlocked; which one of the waiting threads proceeds
|
||||
is not defined, and may vary across implementations.
|
||||
|
||||
All methods are executed atomically.
|
||||
|
||||
|
@ -446,7 +454,8 @@ All methods are executed atomically.
|
|||
|
||||
.. method:: Lock.release()
|
||||
|
||||
Release a lock.
|
||||
Release a lock. This can be called from any thread, not only the thread
|
||||
which has acquired the lock.
|
||||
|
||||
When the lock is locked, reset it to unlocked, and return. If any other threads
|
||||
are blocked waiting for the lock to become unlocked, allow exactly one of them
|
||||
|
@ -468,12 +477,14 @@ and "recursion level" in addition to the locked/unlocked state used by primitive
|
|||
locks. In the locked state, some thread owns the lock; in the unlocked state,
|
||||
no thread owns it.
|
||||
|
||||
To lock the lock, a thread calls its :meth:`acquire` method; this returns once
|
||||
the thread owns the lock. To unlock the lock, a thread calls its
|
||||
:meth:`release` method. :meth:`acquire`/:meth:`release` call pairs may be
|
||||
nested; only the final :meth:`release` (the :meth:`release` of the outermost
|
||||
pair) resets the lock to unlocked and allows another thread blocked in
|
||||
:meth:`acquire` to proceed.
|
||||
To lock the lock, a thread calls its :meth:`~RLock.acquire` method; this
|
||||
returns once the thread owns the lock. To unlock the lock, a thread calls
|
||||
its :meth:`~Lock.release` method. :meth:`~Lock.acquire`/:meth:`~Lock.release`
|
||||
call pairs may be nested; only the final :meth:`~Lock.release` (the
|
||||
:meth:`~Lock.release` of the outermost pair) resets the lock to unlocked and
|
||||
allows another thread blocked in :meth:`~Lock.acquire` to proceed.
|
||||
|
||||
Reentrant locks also support the :ref:`context manager protocol <with-locks>`.
|
||||
|
||||
|
||||
.. method:: RLock.acquire(blocking=True, timeout=-1)
|
||||
|
@ -525,62 +536,74 @@ Condition Objects
|
|||
-----------------
|
||||
|
||||
A condition variable is always associated with some kind of lock; this can be
|
||||
passed in or one will be created by default. (Passing one in is useful when
|
||||
several condition variables must share the same lock.)
|
||||
passed in or one will be created by default. Passing one in is useful when
|
||||
several condition variables must share the same lock. The lock is part of
|
||||
the condition object: you don't have to track it separately.
|
||||
|
||||
A condition variable has :meth:`acquire` and :meth:`release` methods that call
|
||||
the corresponding methods of the associated lock. It also has a :meth:`wait`
|
||||
method, and :meth:`notify` and :meth:`notify_all` methods. These three must only
|
||||
be called when the calling thread has acquired the lock, otherwise a
|
||||
:exc:`RuntimeError` is raised.
|
||||
A condition variable obeys the :ref:`context manager protocol <with-locks>`:
|
||||
using the ``with`` statement acquires the associated lock for the duration of
|
||||
the enclosed block. The :meth:`~Condition.acquire` and
|
||||
:meth:`~Condition.release` methods also call the corresponding methods of
|
||||
the associated lock.
|
||||
|
||||
The :meth:`wait` method releases the lock, and then blocks until it is awakened
|
||||
by a :meth:`notify` or :meth:`notify_all` call for the same condition variable in
|
||||
another thread. Once awakened, it re-acquires the lock and returns. It is also
|
||||
possible to specify a timeout.
|
||||
Other methods must be called with the associated lock held. The
|
||||
:meth:`~Condition.wait` method releases the lock, and then blocks until
|
||||
another thread awakens it by calling :meth:`~Condition.notify` or
|
||||
:meth:`~Condition.notify_all`. Once awakened, :meth:`~Condition.wait`
|
||||
re-acquires the lock and returns. It is also possible to specify a timeout.
|
||||
|
||||
The :meth:`notify` method wakes up one of the threads waiting for the condition
|
||||
variable, if any are waiting. The :meth:`notify_all` method wakes up all threads
|
||||
waiting for the condition variable.
|
||||
The :meth:`~Condition.notify` method wakes up one of the threads waiting for
|
||||
the condition variable, if any are waiting. The :meth:`~Condition.notify_all`
|
||||
method wakes up all threads waiting for the condition variable.
|
||||
|
||||
Note: the :meth:`notify` and :meth:`notify_all` methods don't release the lock;
|
||||
this means that the thread or threads awakened will not return from their
|
||||
:meth:`wait` call immediately, but only when the thread that called
|
||||
:meth:`notify` or :meth:`notify_all` finally relinquishes ownership of the lock.
|
||||
Note: the :meth:`~Condition.notify` and :meth:`~Condition.notify_all` methods
|
||||
don't release the lock; this means that the thread or threads awakened will
|
||||
not return from their :meth:`~Condition.wait` call immediately, but only when
|
||||
the thread that called :meth:`~Condition.notify` or :meth:`~Condition.notify_all`
|
||||
finally relinquishes ownership of the lock.
|
||||
|
||||
Tip: the typical programming style using condition variables uses the lock to
|
||||
|
||||
Usage
|
||||
^^^^^
|
||||
|
||||
The typical programming style using condition variables uses the lock to
|
||||
synchronize access to some shared state; threads that are interested in a
|
||||
particular change of state call :meth:`wait` repeatedly until they see the
|
||||
desired state, while threads that modify the state call :meth:`notify` or
|
||||
:meth:`notify_all` when they change the state in such a way that it could
|
||||
possibly be a desired state for one of the waiters. For example, the following
|
||||
code is a generic producer-consumer situation with unlimited buffer capacity::
|
||||
particular change of state call :meth:`~Condition.wait` repeatedly until they
|
||||
see the desired state, while threads that modify the state call
|
||||
:meth:`~Condition.notify` or :meth:`~Condition.notify_all` when they change
|
||||
the state in such a way that it could possibly be a desired state for one
|
||||
of the waiters. For example, the following code is a generic
|
||||
producer-consumer situation with unlimited buffer capacity::
|
||||
|
||||
# Consume one item
|
||||
cv.acquire()
|
||||
while not an_item_is_available():
|
||||
cv.wait()
|
||||
get_an_available_item()
|
||||
cv.release()
|
||||
with cv:
|
||||
while not an_item_is_available():
|
||||
cv.wait()
|
||||
get_an_available_item()
|
||||
|
||||
# Produce one item
|
||||
cv.acquire()
|
||||
make_an_item_available()
|
||||
cv.notify()
|
||||
cv.release()
|
||||
with cv:
|
||||
make_an_item_available()
|
||||
|
||||
To choose between :meth:`notify` and :meth:`notify_all`, consider whether one
|
||||
state change can be interesting for only one or several waiting threads. E.g.
|
||||
in a typical producer-consumer situation, adding one item to the buffer only
|
||||
needs to wake up one consumer thread.
|
||||
The ``while`` loop checking for the application's condition is necessary
|
||||
because :meth:`~Condition.wait` can return after an arbitrary long time,
|
||||
and other threads may have exhausted the available items in between. This
|
||||
is inherent to multi-threaded programming. The :meth:`~Condition.wait_for`
|
||||
method can be used to automate the condition checking::
|
||||
|
||||
Note: Condition variables can be, depending on the implementation, subject
|
||||
to both spurious wakeups (when :meth:`wait` returns without a :meth:`notify`
|
||||
call) and stolen wakeups (when another thread acquires the lock before the
|
||||
awoken thread.) For this reason, it is always necessary to verify the state
|
||||
the thread is waiting for when :meth:`wait` returns and optionally repeat
|
||||
the call as often as necessary.
|
||||
# Consume an item
|
||||
with cv:
|
||||
cv.wait_for(an_item_is_available)
|
||||
get_an_available_item()
|
||||
|
||||
To choose between :meth:`~Condition.notify` and :meth:`~Condition.notify_all`,
|
||||
consider whether one state change can be interesting for only one or several
|
||||
waiting threads. E.g. in a typical producer-consumer situation, adding one
|
||||
item to the buffer only needs to wake up one consumer thread.
|
||||
|
||||
|
||||
Interface
|
||||
^^^^^^^^^
|
||||
|
||||
.. class:: Condition(lock=None)
|
||||
|
||||
|
@ -648,12 +671,6 @@ the call as often as necessary.
|
|||
held when called and is re-aquired on return. The predicate is evaluated
|
||||
with the lock held.
|
||||
|
||||
Using this method, the consumer example above can be written thus::
|
||||
|
||||
with cv:
|
||||
cv.wait_for(an_item_is_available)
|
||||
get_an_available_item()
|
||||
|
||||
.. versionadded:: 3.2
|
||||
|
||||
.. method:: notify(n=1)
|
||||
|
@ -689,12 +706,16 @@ Semaphore Objects
|
|||
|
||||
This is one of the oldest synchronization primitives in the history of computer
|
||||
science, invented by the early Dutch computer scientist Edsger W. Dijkstra (he
|
||||
used :meth:`P` and :meth:`V` instead of :meth:`acquire` and :meth:`release`).
|
||||
used the names ``P()`` and ``V()`` instead of :meth:`~Semaphore.acquire` and
|
||||
:meth:`~Semaphore.release`).
|
||||
|
||||
A semaphore manages an internal counter which is decremented by each
|
||||
:meth:`acquire` call and incremented by each :meth:`release` call. The counter
|
||||
can never go below zero; when :meth:`acquire` finds that it is zero, it blocks,
|
||||
waiting until some other thread calls :meth:`release`.
|
||||
:meth:`~Semaphore.acquire` call and incremented by each :meth:`~Semaphore.release`
|
||||
call. The counter can never go below zero; when :meth:`~Semaphore.acquire`
|
||||
finds that it is zero, it blocks, waiting until some other thread calls
|
||||
:meth:`~Semaphore.release`.
|
||||
|
||||
Semaphores also support the :ref:`context manager protocol <with-locks>`.
|
||||
|
||||
|
||||
.. class:: Semaphore(value=1)
|
||||
|
@ -710,11 +731,12 @@ waiting until some other thread calls :meth:`release`.
|
|||
When invoked without arguments: if the internal counter is larger than
|
||||
zero on entry, decrement it by one and return immediately. If it is zero
|
||||
on entry, block, waiting until some other thread has called
|
||||
:meth:`release` to make it larger than zero. This is done with proper
|
||||
interlocking so that if multiple :meth:`acquire` calls are blocked,
|
||||
:meth:`release` will wake exactly one of them up. The implementation may
|
||||
pick one at random, so the order in which blocked threads are awakened
|
||||
should not be relied on. Returns true (or blocks indefinitely).
|
||||
:meth:`~Semaphore.release` to make it larger than zero. This is done
|
||||
with proper interlocking so that if multiple :meth:`acquire` calls are
|
||||
blocked, :meth:`~Semaphore.release` will wake exactly one of them up.
|
||||
The implementation may pick one at random, so the order in which
|
||||
blocked threads are awakened should not be relied on. Returns
|
||||
true (or blocks indefinitely).
|
||||
|
||||
When invoked with *blocking* set to false, do not block. If a call
|
||||
without an argument would block, return false immediately; otherwise,
|
||||
|
@ -751,11 +773,12 @@ main thread would initialize the semaphore::
|
|||
Once spawned, worker threads call the semaphore's acquire and release methods
|
||||
when they need to connect to the server::
|
||||
|
||||
pool_sema.acquire()
|
||||
conn = connectdb()
|
||||
... use connection ...
|
||||
conn.close()
|
||||
pool_sema.release()
|
||||
with pool_sema:
|
||||
conn = connectdb()
|
||||
try:
|
||||
... use connection ...
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
The use of a bounded semaphore reduces the chance that a programming error which
|
||||
causes the semaphore to be released more than it's acquired will go undetected.
|
||||
|
@ -770,8 +793,8 @@ This is one of the simplest mechanisms for communication between threads: one
|
|||
thread signals an event and other threads wait for it.
|
||||
|
||||
An event object manages an internal flag that can be set to true with the
|
||||
:meth:`~Event.set` method and reset to false with the :meth:`clear` method. The
|
||||
:meth:`wait` method blocks until the flag is true.
|
||||
:meth:`~Event.set` method and reset to false with the :meth:`~Event.clear`
|
||||
method. The :meth:`~Event.wait` method blocks until the flag is true.
|
||||
|
||||
|
||||
.. class:: Event()
|
||||
|
@ -798,7 +821,7 @@ An event object manages an internal flag that can be set to true with the
|
|||
|
||||
Block until the internal flag is true. If the internal flag is true on
|
||||
entry, return immediately. Otherwise, block until another thread calls
|
||||
:meth:`set` to set the flag to true, or until the optional timeout occurs.
|
||||
:meth:`.set` to set the flag to true, or until the optional timeout occurs.
|
||||
|
||||
When the timeout argument is present and not ``None``, it should be a
|
||||
floating point number specifying a timeout for the operation in seconds
|
||||
|
@ -854,8 +877,8 @@ Barrier Objects
|
|||
|
||||
This class provides a simple synchronization primitive for use by a fixed number
|
||||
of threads that need to wait for each other. Each of the threads tries to pass
|
||||
the barrier by calling the :meth:`wait` method and will block until all of the
|
||||
threads have made the call. At this points, the threads are released
|
||||
the barrier by calling the :meth:`~Barrier.wait` method and will block until
|
||||
all of the threads have made the call. At this points, the threads are released
|
||||
simultanously.
|
||||
|
||||
The barrier can be reused any number of times for the same number of threads.
|
||||
|
@ -956,19 +979,24 @@ Using locks, conditions, and semaphores in the :keyword:`with` statement
|
|||
|
||||
All of the objects provided by this module that have :meth:`acquire` and
|
||||
:meth:`release` methods can be used as context managers for a :keyword:`with`
|
||||
statement. The :meth:`acquire` method will be called when the block is entered,
|
||||
and :meth:`release` will be called when the block is exited.
|
||||
statement. 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::
|
||||
|
||||
with some_lock:
|
||||
# do something...
|
||||
|
||||
is equivalent to::
|
||||
|
||||
some_lock.acquire()
|
||||
try:
|
||||
# do something...
|
||||
finally:
|
||||
some_lock.release()
|
||||
|
||||
Currently, :class:`Lock`, :class:`RLock`, :class:`Condition`,
|
||||
:class:`Semaphore`, and :class:`BoundedSemaphore` objects may be used as
|
||||
:keyword:`with` statement context managers. For example::
|
||||
|
||||
import threading
|
||||
|
||||
some_rlock = threading.RLock()
|
||||
|
||||
with some_rlock:
|
||||
print("some_rlock is locked while this executes")
|
||||
:keyword:`with` statement context managers.
|
||||
|
||||
|
||||
.. _threaded-imports:
|
||||
|
|
Loading…
Reference in New Issue