diff --git a/Doc/library/concurrent.interpreters.rst b/Doc/library/concurrent.interpreters.rst index 2c509ab13a6acfd..1d126451c49c5ae 100644 --- a/Doc/library/concurrent.interpreters.rst +++ b/Doc/library/concurrent.interpreters.rst @@ -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) @@ -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() diff --git a/Doc/whatsnew/3.16.rst b/Doc/whatsnew/3.16.rst index c61190822234ead..eea870ab2598c44 100644 --- a/Doc/whatsnew/3.16.rst +++ b/Doc/whatsnew/3.16.rst @@ -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 --- diff --git a/Lib/concurrent/interpreters/__init__.py b/Lib/concurrent/interpreters/__init__.py index 335a744b727d100..3053bfbcb122771 100644 --- a/Lib/concurrent/interpreters/__init__.py +++ b/Lib/concurrent/interpreters/__init__.py @@ -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__. diff --git a/Lib/test/test_interpreters/test_api.py b/Lib/test/test_interpreters/test_api.py index aac3cdd717668ca..a9e296891bb8705 100644 --- a/Lib/test/test_interpreters/test_api.py +++ b/Lib/test/test_interpreters/test_api.py @@ -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): diff --git a/Misc/NEWS.d/next/Library/2026-09-28-00-00-00.gh-issue-158370.ctxmgr.rst b/Misc/NEWS.d/next/Library/2026-09-28-00-00-00.gh-issue-158370.ctxmgr.rst new file mode 100644 index 000000000000000..f4b9e2469daa3ed --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-09-28-00-00-00.gh-issue-158370.ctxmgr.rst @@ -0,0 +1,2 @@ +:class:`concurrent.interpreters.Interpreter` objects now support the +:term:`context manager` protocol.