Skip to content

bpo-30439: Add a basic high-level interpreters module. #1803

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
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
90 changes: 90 additions & 0 deletions Doc/library/_interpreters.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
:mod:`_interpreters` --- Low-level interpreters API
===================================================

.. module:: _interpreters
:synopsis: Low-level interpreters API.

.. versionadded:: 3,7

--------------

This module provides low-level primitives for working with multiple
Python interpreters in the same runtime in the current process.

More information about (sub)interpreters is found at
:ref:`sub-interpreter-support`, including what data is shared between
interpreters and what is unique. Note particularly that interpreters
aren't inherently threaded, even though they track and manage Python
threads. To run code in an interpreter in a different OS thread, call
:func:`run_string` in a function that you run in a new Python thread.
For example::

id = _interpreters.create()
def f():
_interpreters.run_string(id, 'print("in a thread")')

t = threading.Thread(target=f)
t.start()

This module is optional. It is provided by Python implementations which
support multiple interpreters.

It defines the following functions:

.. function:: enumerate()

Return a list of the IDs of every existing interpreter.


.. function:: get_current()

Return the ID of the currently running interpreter.


.. function:: get_main()

Return the ID of the main interpreter.


.. function:: is_running(id)

Return whether or not the identified interpreter is currently
running any code.


.. function:: create()

Initialize a new Python interpreter and return its identifier. The
interpreter will be created in the current thread and will remain
idle until something is run in it.


.. function:: destroy(id)

Finalize and destroy the identified interpreter.


.. function:: run_string(id, command)

A wrapper around :c:func:`PyRun_SimpleString` which runs the provided
Python program in the main thread of the identified interpreter.
Providing an invalid or unknown ID results in a RuntimeError,
likewise if the main interpreter or any other running interpreter
is used.

Any value returned from the code is thrown away, similar to what
threads do. If the code results in an exception then that exception
is raised in the thread in which run_string() was called, similar to
how :func:`exec` works. This aligns with how interpreters are not
inherently threaded. Note that SystemExit (as raised by sys.exit())
is not treated any differently and will result in the process ending
if not caught explicitly.


.. function:: run_string_unrestricted(id, command, ns=None)

Like :c:func:`run_string` but returns the dict in which the code
was executed. It also supports providing a namespace that gets
merged into the execution namespace before execution. Note that
this allows objects to leak between interpreters, which may not
be desirable.
1 change: 1 addition & 0 deletions Doc/library/concurrency.rst
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,4 @@ The following are support modules for some of the above services:
dummy_threading.rst
_thread.rst
_dummy_thread.rst
_interpreters.rst
64 changes: 64 additions & 0 deletions Doc/library/interpreters.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
:mod:`interpreters` --- high-level interpreters API
===================================================

.. module:: interpreters
:synopsis: High-level interpreters API.

**Source code:** :source:`Lib/interpreters.py`

.. versionadded:: 3,7

--------------

This module provides high-level interfaces for interacting with
the Python interpreters. It is built on top of the lower- level
:mod:`_interpreters` module.

.. XXX Summarize :ref:`_sub-interpreter-support` here.

This module defines the following functions:

.. function:: enumerate()

Return a list of all existing interpreters.


.. function:: get_current()

Return the currently running interpreter.


.. function:: get_main()

Return the main interpreter.


.. function:: create()

Initialize a new Python interpreter and return it. The
interpreter will be created in the current thread and will remain
idle until something is run in it.


This module also defines the following class:

.. class:: Interpreter(id)

``id`` is the interpreter's ID.

.. property:: id

The interpreter's ID.

.. method:: is_running()

Return whether or not the interpreter is currently running.

.. method:: destroy()

Finalize and destroy the interpreter.

.. method:: run(code)

Run the provided Python code in the interpreter, in the current
OS thread. Supported code: source text.
79 changes: 79 additions & 0 deletions Lib/interpreters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
"""Interpreters module providing a high-level API to Python interpreters."""

import _interpreters


__all__ = ['enumerate', 'current', 'main', 'create', 'Interpreter']


def enumerate():
"""Return a list of all existing interpreters."""
return [Interpreter(id)
for id in _interpreters.enumerate()]


def current():
"""Return the currently running interpreter."""
id = _interpreters.get_current()
return Interpreter(id)


def main():
"""Return the main interpreter."""
id = _interpreters.get_main()
return Interpreter(id)


def create():
"""Return a new Interpreter.

The interpreter is created in the current OS thread. It will remain
idle until its run() method is called.
"""
id = _interpreters.create()
return Interpreter(id)


class Interpreter:
"""A single Python interpreter in the current runtime."""

def __init__(self, id):
self._id = id

def __repr__(self):
return '{}(id={!r})'.format(type(self).__name__, self._id)

def __eq__(self, other):
try:
other_id = other.id
except AttributeError:
return False
return self._id == other_id

@property
def id(self):
"""The interpreter's ID."""
return self._id

def is_running(self):
"""Return whether or not the interpreter is currently running."""
return _interpreters.is_running(self._id)

def destroy(self):
"""Finalize and destroy the interpreter.

After calling destroy(), all operations on this interpreter
will fail.
"""
_interpreters.destroy(self._id)

def run(self, code):
"""Run the provided Python code in this interpreter.

If the code is a string then it will be run as it would by
calling exec(). Other kinds of code are not supported.
"""
if isinstance(code, str):
_interpreters.run_string(self._id, code)
else:
raise NotImplementedError
Loading