Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 30 additions & 21 deletions Doc/library/concurrent.interpreters.rst
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,16 @@ Interpreter objects
Generally, :class:`Interpreter` shouldn't be called directly.
Instead, use :func:`create` or one of the other module functions.

:class:`!Interpreter` objects support the :term:`context manager`
protocol. Exiting the :keyword:`with` block calls :meth:`close`::

with interpreters.create() as interp:
interp.exec('print("hello")')

.. versionchanged:: next
Added support for using :class:`!Interpreter` objects in
a :term:`context manager`.

.. attribute:: id

(read-only)
Expand Down Expand Up @@ -352,33 +362,32 @@ Creating an interpreter and running code in it::

from concurrent import interpreters

interp = interpreters.create()

# Run in the current OS thread.
with interpreters.create() as interp:
# Run in the current OS thread.

interp.exec('print("spam!")')
interp.exec('print("spam!")')

interp.exec("""if True:
print('spam!')
""")
interp.exec("""if True:
print('spam!')
""")

from textwrap import dedent
interp.exec(dedent("""
print('spam!')
"""))
from textwrap import dedent
interp.exec(dedent("""
print('spam!')
"""))

def run(arg):
return arg
def run(arg):
return arg

res = interp.call(run, 'spam!')
print(res)
res = interp.call(run, 'spam!')
print(res)

def run():
print('spam!')
def run():
print('spam!')

interp.call(run)
interp.call(run)

# Run in new OS thread.
# Run in new OS thread.

t = interp.call_in_thread(run)
t.join()
t = interp.call_in_thread(run)
t.join()
7 changes: 7 additions & 0 deletions Doc/whatsnew/3.16.rst
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,13 @@ concurrent.futures
Use method :meth:`!close` to explicitly close the iterator.
(Contributed by xzmeng and Serhiy Storchaka in :gh:`108518`.)

concurrent.interpreters
-----------------------

* :class:`~concurrent.interpreters.Interpreter` objects now support the
:term:`context manager` protocol. Exiting the :keyword:`with` block calls
:meth:`~concurrent.interpreters.Interpreter.close`.
(Contributed by Peter Bierma in :gh:`158370`.)

csv
---
Expand Down
6 changes: 6 additions & 0 deletions Lib/concurrent/interpreters/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,12 @@ def close(self):
"""
return _interpreters.destroy(self._id, restrict=True)

def __enter__(self):
return self

def __exit__(self, *exc_info):
self.close()

def prepare_main(self, ns=None, /, **kwargs):
"""Bind the given values into the interpreter's __main__.

Expand Down
25 changes: 25 additions & 0 deletions Lib/test/test_interpreters/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -832,6 +832,31 @@ def task():
assert_python_ok('-c', code)


class TestInterpreterContextManager(TestBase):

def test_basic(self):
main, = interpreters.list_all()
with interpreters.create() as interp:
self.assertIsInstance(interp, interpreters.Interpreter)
self.assertEqual(interpreters.list_all(), [main, interp])
self.assertEqual(interpreters.list_all(), [main])
with self.assertRaises(InterpreterNotFoundError):
interp.exec('pass')

def test_exception(self):
main, = interpreters.list_all()
with self.assertRaises(ZeroDivisionError):
with interpreters.create() as interp:
1/0
self.assertEqual(interpreters.list_all(), [main])

def test_main(self):
main, = interpreters.list_all()
with self.assertRaises(InterpreterError):
with main:
pass


class TestInterpreterPrepareMain(TestBase):

def test_empty(self):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
:class:`concurrent.interpreters.Interpreter` objects now support the
:term:`context manager` protocol.
Loading