CINXE.COM
PEP 734 – Multiple Interpreters in the Stdlib | peps.python.org
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="color-scheme" content="light dark"> <title>PEP 734 – Multiple Interpreters in the Stdlib | peps.python.org</title> <link rel="shortcut icon" href="../_static/py.png"> <link rel="canonical" href="https://peps.python.org/pep-0734/"> <link rel="stylesheet" href="../_static/style.css" type="text/css"> <link rel="stylesheet" href="../_static/mq.css" type="text/css"> <link rel="stylesheet" href="../_static/pygments.css" type="text/css" media="(prefers-color-scheme: light)" id="pyg-light"> <link rel="stylesheet" href="../_static/pygments_dark.css" type="text/css" media="(prefers-color-scheme: dark)" id="pyg-dark"> <link rel="alternate" type="application/rss+xml" title="Latest PEPs" href="https://peps.python.org/peps.rss"> <meta property="og:title" content='PEP 734 – Multiple Interpreters in the Stdlib | peps.python.org'> <meta property="og:description" content="This PEP proposes to add a new module, interpreters, to support inspecting, creating, and running code in multiple interpreters in the current process. This includes Interpreter objects that represent the underlying interpreters. The module will also ..."> <meta property="og:type" content="website"> <meta property="og:url" content="https://peps.python.org/pep-0734/"> <meta property="og:site_name" content="Python Enhancement Proposals (PEPs)"> <meta property="og:image" content="https://peps.python.org/_static/og-image.png"> <meta property="og:image:alt" content="Python PEPs"> <meta property="og:image:width" content="200"> <meta property="og:image:height" content="200"> <meta name="description" content="This PEP proposes to add a new module, interpreters, to support inspecting, creating, and running code in multiple interpreters in the current process. This includes Interpreter objects that represent the underlying interpreters. The module will also ..."> <meta name="theme-color" content="#3776ab"> </head> <body> <svg xmlns="http://www.w3.org/2000/svg" style="display: none;"> <symbol id="svg-sun-half" viewBox="0 0 24 24" pointer-events="all"> <title>Following system colour scheme</title> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <circle cx="12" cy="12" r="9"></circle> <path d="M12 3v18m0-12l4.65-4.65M12 14.3l7.37-7.37M12 19.6l8.85-8.85"></path> </svg> </symbol> <symbol id="svg-moon" viewBox="0 0 24 24" pointer-events="all"> <title>Selected dark colour scheme</title> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <path stroke="none" d="M0 0h24v24H0z" fill="none"></path> <path d="M12 3c.132 0 .263 0 .393 0a7.5 7.5 0 0 0 7.92 12.446a9 9 0 1 1 -8.313 -12.454z"></path> </svg> </symbol> <symbol id="svg-sun" viewBox="0 0 24 24" pointer-events="all"> <title>Selected light colour scheme</title> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <circle cx="12" cy="12" r="5"></circle> <line x1="12" y1="1" x2="12" y2="3"></line> <line x1="12" y1="21" x2="12" y2="23"></line> <line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line> <line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line> <line x1="1" y1="12" x2="3" y2="12"></line> <line x1="21" y1="12" x2="23" y2="12"></line> <line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line> <line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line> </svg> </symbol> </svg> <script> document.documentElement.dataset.colour_scheme = localStorage.getItem("colour_scheme") || "auto" </script> <section id="pep-page-section"> <header> <h1>Python Enhancement Proposals</h1> <ul class="breadcrumbs"> <li><a href="https://www.python.org/" title="The Python Programming Language">Python</a> » </li> <li><a href="../pep-0000/">PEP Index</a> » </li> <li>PEP 734</li> </ul> <button id="colour-scheme-cycler" onClick="setColourScheme(nextColourScheme())"> <svg aria-hidden="true" class="colour-scheme-icon-when-auto"><use href="#svg-sun-half"></use></svg> <svg aria-hidden="true" class="colour-scheme-icon-when-dark"><use href="#svg-moon"></use></svg> <svg aria-hidden="true" class="colour-scheme-icon-when-light"><use href="#svg-sun"></use></svg> <span class="visually-hidden">Toggle light / dark / auto colour theme</span> </button> </header> <article> <section id="pep-content"> <h1 class="page-title">PEP 734 – Multiple Interpreters in the Stdlib</h1> <dl class="rfc2822 field-list simple"> <dt class="field-odd">Author<span class="colon">:</span></dt> <dd class="field-odd">Eric Snow <ericsnowcurrently at gmail.com></dd> <dt class="field-even">Discussions-To<span class="colon">:</span></dt> <dd class="field-even"><a class="reference external" href="https://discuss.python.org/t/pep-734-multiple-interpreters-in-the-stdlib/41147">Discourse thread</a></dd> <dt class="field-odd">Status<span class="colon">:</span></dt> <dd class="field-odd"><abbr title="Inactive draft that may be taken up again at a later time">Deferred</abbr></dd> <dt class="field-even">Type<span class="colon">:</span></dt> <dd class="field-even"><abbr title="Normative PEP with a new feature for Python, implementation change for CPython or interoperability standard for the ecosystem">Standards Track</abbr></dd> <dt class="field-odd">Created<span class="colon">:</span></dt> <dd class="field-odd">06-Nov-2023</dd> <dt class="field-even">Python-Version<span class="colon">:</span></dt> <dd class="field-even">3.13</dd> <dt class="field-odd">Post-History<span class="colon">:</span></dt> <dd class="field-odd"><a class="reference external" href="https://discuss.python.org/t/pep-734-multiple-interpreters-in-the-stdlib/41147/" title="Discourse thread">14-Dec-2023</a></dd> <dt class="field-even">Replaces<span class="colon">:</span></dt> <dd class="field-even"><a class="reference external" href="../pep-0554/">554</a></dd> <dt class="field-odd">Resolution<span class="colon">:</span></dt> <dd class="field-odd"><a class="reference external" href="https://discuss.python.org/t/pep-734-multiple-interpreters-in-the-stdlib/41147/24">Discourse message</a></dd> </dl> <hr class="docutils" /> <section id="contents"> <details><summary>Table of Contents</summary><ul class="simple"> <li><a class="reference internal" href="#abstract">Abstract</a></li> <li><a class="reference internal" href="#introduction">Introduction</a><ul> <li><a class="reference internal" href="#threads-and-thread-states">Threads and Thread States</a></li> <li><a class="reference internal" href="#interpreter-states">Interpreter States</a></li> <li><a class="reference internal" href="#interpreters-and-threads">Interpreters and Threads</a></li> <li><a class="reference internal" href="#the-main-interpreter">The “Main” Interpreter</a></li> <li><a class="reference internal" href="#interpreter-isolation">Interpreter Isolation</a></li> <li><a class="reference internal" href="#existing-execution-components">Existing Execution Components</a><ul> <li><a class="reference internal" href="#builtins-exec">builtins.exec()</a></li> <li><a class="reference internal" href="#command-line">Command-line</a></li> <li><a class="reference internal" href="#threading-thread">threading.Thread</a></li> </ul> </li> </ul> </li> <li><a class="reference internal" href="#motivation">Motivation</a></li> <li><a class="reference internal" href="#specification">Specification</a><ul> <li><a class="reference internal" href="#using-interpreters">Using Interpreters</a></li> <li><a class="reference internal" href="#interpreter-objects">Interpreter Objects</a></li> <li><a class="reference internal" href="#communicating-between-interpreters">Communicating Between Interpreters</a></li> <li><a class="reference internal" href="#queue-objects">Queue Objects</a></li> <li><a class="reference internal" href="#shareable-objects">Shareable Objects</a></li> <li><a class="reference internal" href="#synchronization">Synchronization</a></li> <li><a class="reference internal" href="#exceptions">Exceptions</a></li> <li><a class="reference internal" href="#interpreterpoolexecutor">InterpreterPoolExecutor</a></li> <li><a class="reference internal" href="#sys-implementation-supports-isolated-interpreters">sys.implementation.supports_isolated_interpreters</a></li> <li><a class="reference internal" href="#examples">Examples</a></li> </ul> </li> <li><a class="reference internal" href="#rationale">Rationale</a><ul> <li><a class="reference internal" href="#a-minimal-api">A Minimal API</a></li> <li><a class="reference internal" href="#create-create-queue">create(), create_queue()</a></li> <li><a class="reference internal" href="#interpreter-prepare-main-sets-multiple-variables">Interpreter.prepare_main() Sets Multiple Variables</a></li> <li><a class="reference internal" href="#propagating-exceptions">Propagating Exceptions</a></li> <li><a class="reference internal" href="#limited-object-sharing">Limited Object Sharing</a></li> <li><a class="reference internal" href="#objects-vs-id-proxies">Objects vs. ID Proxies</a></li> </ul> </li> <li><a class="reference internal" href="#rejected-ideas">Rejected Ideas</a></li> <li><a class="reference internal" href="#copyright">Copyright</a></li> </ul> </details></section> <div class="admonition note"> <p class="admonition-title">Note</p> <p>This PEP is essentially a continuation of <a class="pep reference internal" href="../pep-0554/" title="PEP 554 – Multiple Interpreters in the Stdlib">PEP 554</a>. That document had grown a lot of ancillary information across 7 years of discussion. This PEP is a reduction back to the essential information. Much of that extra information is still valid and useful, just not in the immediate context of the specific proposal here.</p> </div> <section id="abstract"> <h2><a class="toc-backref" href="#abstract" role="doc-backlink">Abstract</a></h2> <p>This PEP proposes to add a new module, <code class="docutils literal notranslate"><span class="pre">interpreters</span></code>, to support inspecting, creating, and running code in multiple interpreters in the current process. This includes <code class="docutils literal notranslate"><span class="pre">Interpreter</span></code> objects that represent the underlying interpreters. The module will also provide a basic <code class="docutils literal notranslate"><span class="pre">Queue</span></code> class for communication between interpreters. Finally, we will add a new <code class="docutils literal notranslate"><span class="pre">concurrent.futures.InterpreterPoolExecutor</span></code> based on the <code class="docutils literal notranslate"><span class="pre">interpreters</span></code> module.</p> </section> <section id="introduction"> <h2><a class="toc-backref" href="#introduction" role="doc-backlink">Introduction</a></h2> <p>Fundamentally, an “interpreter” is the collection of (essentially) all runtime state which Python threads must share. So, let’s first look at threads. Then we’ll circle back to interpreters.</p> <section id="threads-and-thread-states"> <h3><a class="toc-backref" href="#threads-and-thread-states" role="doc-backlink">Threads and Thread States</a></h3> <p>A Python process will have one or more OS threads running Python code (or otherwise interacting with the C API). Each of these threads interacts with the CPython runtime using its own thread state (<code class="docutils literal notranslate"><span class="pre">PyThreadState</span></code>), which holds all the runtime state unique to that thread. There is also some runtime state that is shared between multiple OS threads.</p> <p>Any OS thread may switch which thread state it is currently using, as long as it isn’t one that another OS thread is already using (or has been using). This “current” thread state is stored by the runtime in a thread-local variable, and may be looked up explicitly with <code class="docutils literal notranslate"><span class="pre">PyThreadState_Get()</span></code>. It gets set automatically for the initial (“main”) OS thread and for <code class="docutils literal notranslate"><span class="pre">threading.Thread</span></code> objects. From the C API it is set (and cleared) by <code class="docutils literal notranslate"><span class="pre">PyThreadState_Swap()</span></code> and may be set by <code class="docutils literal notranslate"><span class="pre">PyGILState_Ensure()</span></code>. Most of the C API requires that there be a current thread state, either looked up implicitly or passed in as an argument.</p> <p>The relationship between OS threads and thread states is one-to-many. Each thread state is associated with at most a single OS thread and records its thread ID. A thread state is never used for more than one OS thread. In the other direction, however, an OS thread may have more than one thread state associated with it, though, again, only one may be current.</p> <p>When there’s more than one thread state for an OS thread, <code class="docutils literal notranslate"><span class="pre">PyThreadState_Swap()</span></code> is used in that OS thread to switch between them, with the requested thread state becoming the current one. Whatever was running in the thread using the old thread state is effectively paused until that thread state is swapped back in.</p> </section> <section id="interpreter-states"> <h3><a class="toc-backref" href="#interpreter-states" role="doc-backlink">Interpreter States</a></h3> <p>As noted earlier, there is some runtime state that multiple OS threads share. Some of it is exposed by the <code class="docutils literal notranslate"><span class="pre">sys</span></code> module, though much is used internally and not exposed explicitly or only through the C API.</p> <p>This shared state is called the interpreter state (<code class="docutils literal notranslate"><span class="pre">PyInterpreterState</span></code>). We’ll sometimes refer to it here as just “interpreter”, though that is also sometimes used to refer to the <code class="docutils literal notranslate"><span class="pre">python</span></code> executable, to the Python implementation, and to the bytecode interpreter (i.e. <code class="docutils literal notranslate"><span class="pre">exec()</span></code>/<code class="docutils literal notranslate"><span class="pre">eval()</span></code>).</p> <p>CPython has supported multiple interpreters in the same process (AKA “subinterpreters”) since version 1.5 (1997). The feature has been available via the <a class="reference external" href="https://docs.python.org/3/c-api/init.html#sub-interpreter-support" title="(in Python v3.13)"><span class="xref std std-ref">C API</span></a>.</p> </section> <section id="interpreters-and-threads"> <h3><a class="toc-backref" href="#interpreters-and-threads" role="doc-backlink">Interpreters and Threads</a></h3> <p>Thread states are related to interpreter states in much the same way that OS threads and processes are related (at a high level). To begin with, the relationship is one-to-many. A thread state belongs to a single interpreter (and stores a pointer to it). That thread state is never used for a different interpreter. In the other direction, however, an interpreter may have zero or more thread states associated with it. The interpreter is only considered active in OS threads where one of its thread states is current.</p> <p>Interpreters are created via the C API using <code class="docutils literal notranslate"><span class="pre">Py_NewInterpreterFromConfig()</span></code> (or <code class="docutils literal notranslate"><span class="pre">Py_NewInterpreter()</span></code>, which is a light wrapper around <code class="docutils literal notranslate"><span class="pre">Py_NewInterpreterFromConfig()</span></code>). That function does the following:</p> <ol class="arabic simple"> <li>create a new interpreter state</li> <li>create a new thread state</li> <li>set the thread state as current (a current tstate is needed for interpreter init)</li> <li>initialize the interpreter state using that thread state</li> <li>return the thread state (still current)</li> </ol> <p>Note that the returned thread state may be immediately discarded. There is no requirement that an interpreter have any thread states, except as soon as the interpreter is meant to actually be used. At that point it must be made active in the current OS thread.</p> <p>To make an existing interpreter active in the current OS thread, the C API user first makes sure that interpreter has a corresponding thread state. Then <code class="docutils literal notranslate"><span class="pre">PyThreadState_Swap()</span></code> is called like normal using that thread state. If the thread state for another interpreter was already current then it gets swapped out like normal and execution of that interpreter in the OS thread is thus effectively paused until it is swapped back in.</p> <p>Once an interpreter is active in the current OS thread like that, the thread can call any of the C API, such as <code class="docutils literal notranslate"><span class="pre">PyEval_EvalCode()</span></code> (i.e. <code class="docutils literal notranslate"><span class="pre">exec()</span></code>). This works by using the current thread state as the runtime context.</p> </section> <section id="the-main-interpreter"> <h3><a class="toc-backref" href="#the-main-interpreter" role="doc-backlink">The “Main” Interpreter</a></h3> <p>When a Python process starts, it creates a single interpreter state (the “main” interpreter) with a single thread state for the current OS thread. The Python runtime is then initialized using them.</p> <p>After initialization, the script or module or REPL is executed using them. That execution happens in the interpreter’s <code class="docutils literal notranslate"><span class="pre">__main__</span></code> module.</p> <p>When the process finishes running the requested Python code or REPL, in the main OS thread, the Python runtime is finalized in that thread using the main interpreter.</p> <p>Runtime finalization has only a slight, indirect effect on still-running Python threads, whether in the main interpreter or in subinterpreters. That’s because right away it waits indefinitely for all non-daemon Python threads to finish.</p> <p>While the C API may be queried, there is no mechanism by which any Python thread is directly alerted that finalization has begun, other than perhaps with “atexit” functions that may be been registered using <code class="docutils literal notranslate"><span class="pre">threading._register_atexit()</span></code>.</p> <p>Any remaining subinterpreters are themselves finalized later, but at that point they aren’t current in any OS threads.</p> </section> <section id="interpreter-isolation"> <h3><a class="toc-backref" href="#interpreter-isolation" role="doc-backlink">Interpreter Isolation</a></h3> <p>CPython’s interpreters are intended to be strictly isolated from each other. That means interpreters never share objects (except in very specific cases with immortal, immutable builtin objects). Each interpreter has its own modules (<code class="docutils literal notranslate"><span class="pre">sys.modules</span></code>), classes, functions, and variables. Even where two interpreters define the same class, each will have its own copy. The same applies to state in C, including in extension modules. The CPython C API docs <a class="reference external" href="https://docs.python.org/3/c-api/init.html#bugs-and-caveats">explain more</a>.</p> <p>Notably, there is some process-global state that interpreters will always share, some mutable and some immutable. Sharing immutable state presents few problems, while providing some benefits (mainly performance). However, all shared mutable state requires special management, particularly for thread-safety, some of which the OS takes care of for us.</p> <p>Mutable:</p> <ul class="simple"> <li>file descriptors</li> <li>low-level env vars</li> <li>process memory (though allocators <em>are</em> isolated)</li> <li>the list of interpreters</li> </ul> <p>Immutable:</p> <ul class="simple"> <li>builtin types (e.g. <code class="docutils literal notranslate"><span class="pre">dict</span></code>, <code class="docutils literal notranslate"><span class="pre">bytes</span></code>)</li> <li>singletons (e.g. <code class="docutils literal notranslate"><span class="pre">None</span></code>)</li> <li>underlying static module data (e.g. functions) for builtin/extension/frozen modules</li> </ul> </section> <section id="existing-execution-components"> <h3><a class="toc-backref" href="#existing-execution-components" role="doc-backlink">Existing Execution Components</a></h3> <p>There are a number of existing parts of Python that may help with understanding how code may be run in a subinterpreter.</p> <p>In CPython, each component is built around one of the following C API functions (or variants):</p> <ul class="simple"> <li><code class="docutils literal notranslate"><span class="pre">PyEval_EvalCode()</span></code>: run the bytecode interpreter with the given code object</li> <li><code class="docutils literal notranslate"><span class="pre">PyRun_String()</span></code>: compile + <code class="docutils literal notranslate"><span class="pre">PyEval_EvalCode()</span></code></li> <li><code class="docutils literal notranslate"><span class="pre">PyRun_File()</span></code>: read + compile + <code class="docutils literal notranslate"><span class="pre">PyEval_EvalCode()</span></code></li> <li><code class="docutils literal notranslate"><span class="pre">PyRun_InteractiveOneObject()</span></code>: compile + <code class="docutils literal notranslate"><span class="pre">PyEval_EvalCode()</span></code></li> <li><code class="docutils literal notranslate"><span class="pre">PyObject_Call()</span></code>: calls <code class="docutils literal notranslate"><span class="pre">PyEval_EvalCode()</span></code></li> </ul> <section id="builtins-exec"> <h4><a class="toc-backref" href="#builtins-exec" role="doc-backlink">builtins.exec()</a></h4> <p>The builtin <code class="docutils literal notranslate"><span class="pre">exec()</span></code> may be used to execute Python code. It is essentially a wrapper around the C API functions <code class="docutils literal notranslate"><span class="pre">PyRun_String()</span></code> and <code class="docutils literal notranslate"><span class="pre">PyEval_EvalCode()</span></code>.</p> <p>Here are some relevant characteristics of the builtin <code class="docutils literal notranslate"><span class="pre">exec()</span></code>:</p> <ul class="simple"> <li>It runs in the current OS thread and pauses whatever was running there, which resumes when <code class="docutils literal notranslate"><span class="pre">exec()</span></code> finishes. No other OS threads are affected. (To avoid pausing the current Python thread, run <code class="docutils literal notranslate"><span class="pre">exec()</span></code> in a <code class="docutils literal notranslate"><span class="pre">threading.Thread</span></code>.)</li> <li>It may start additional threads, which don’t interrupt it.</li> <li>It executes against a “globals” namespace (and a “locals” namespace). At module-level, <code class="docutils literal notranslate"><span class="pre">exec()</span></code> defaults to using <code class="docutils literal notranslate"><span class="pre">__dict__</span></code> of the current module (i.e. <code class="docutils literal notranslate"><span class="pre">globals()</span></code>). <code class="docutils literal notranslate"><span class="pre">exec()</span></code> uses that namespace as-is and does not clear it before or after.</li> <li>It propagates any uncaught exception from the code it ran. The exception is raised from the <code class="docutils literal notranslate"><span class="pre">exec()</span></code> call in the Python thread that originally called <code class="docutils literal notranslate"><span class="pre">exec()</span></code>.</li> </ul> </section> <section id="command-line"> <h4><a class="toc-backref" href="#command-line" role="doc-backlink">Command-line</a></h4> <p>The <code class="docutils literal notranslate"><span class="pre">python</span></code> CLI provides several ways to run Python code. In each case it maps to a corresponding C API call:</p> <ul class="simple"> <li><code class="docutils literal notranslate"><span class="pre"><no</span> <span class="pre">args></span></code>, <code class="docutils literal notranslate"><span class="pre">-i</span></code> - run the REPL (<code class="docutils literal notranslate"><span class="pre">PyRun_InteractiveOneObject()</span></code>)</li> <li><code class="docutils literal notranslate"><span class="pre"><filename></span></code> - run a script (<code class="docutils literal notranslate"><span class="pre">PyRun_File()</span></code>)</li> <li><code class="docutils literal notranslate"><span class="pre">-c</span> <span class="pre"><code></span></code> - run the given Python code (<code class="docutils literal notranslate"><span class="pre">PyRun_String()</span></code>)</li> <li><code class="docutils literal notranslate"><span class="pre">-m</span> <span class="pre">module</span></code> - run the module as a script (<code class="docutils literal notranslate"><span class="pre">PyEval_EvalCode()</span></code> via <code class="docutils literal notranslate"><span class="pre">runpy._run_module_as_main()</span></code>)</li> </ul> <p>In each case it is essentially a variant of running <code class="docutils literal notranslate"><span class="pre">exec()</span></code> at the top-level of the <code class="docutils literal notranslate"><span class="pre">__main__</span></code> module of the main interpreter.</p> </section> <section id="threading-thread"> <h4><a class="toc-backref" href="#threading-thread" role="doc-backlink">threading.Thread</a></h4> <p>When a Python thread is started, it runs the “target” function with <code class="docutils literal notranslate"><span class="pre">PyObject_Call()</span></code> using a new thread state. The globals namespace come from <code class="docutils literal notranslate"><span class="pre">func.__globals__</span></code> and any uncaught exception is discarded.</p> </section> </section> </section> <section id="motivation"> <h2><a class="toc-backref" href="#motivation" role="doc-backlink">Motivation</a></h2> <p>The <code class="docutils literal notranslate"><span class="pre">interpreters</span></code> module will provide a high-level interface to the multiple interpreter functionality. The goal is to make the existing multiple-interpreters feature of CPython more easily accessible to Python code. This is particularly relevant now that CPython has a per-interpreter GIL (<a class="pep reference internal" href="../pep-0684/" title="PEP 684 – A Per-Interpreter GIL">PEP 684</a>) and people are more interested in using multiple interpreters.</p> <p>Without a stdlib module, users are limited to the <a class="reference external" href="https://docs.python.org/3/c-api/init.html#sub-interpreter-support" title="(in Python v3.13)"><span class="xref std std-ref">C API</span></a>, which restricts how much they can try out and take advantage of multiple interpreters.</p> <p>The module will include a basic mechanism for communicating between interpreters. Without one, multiple interpreters are a much less useful feature.</p> </section> <section id="specification"> <h2><a class="toc-backref" href="#specification" role="doc-backlink">Specification</a></h2> <p>The module will:</p> <ul class="simple"> <li>expose the existing multiple interpreter support</li> <li>introduce a basic mechanism for communicating between interpreters</li> </ul> <p>The module will wrap a new low-level <code class="docutils literal notranslate"><span class="pre">_interpreters</span></code> module (in the same way as the <code class="docutils literal notranslate"><span class="pre">threading</span></code> module). However, that low-level API is not intended for public use and thus not part of this proposal.</p> <section id="using-interpreters"> <h3><a class="toc-backref" href="#using-interpreters" role="doc-backlink">Using Interpreters</a></h3> <p>The module defines the following functions:</p> <ul class="simple"> <li><dl class="simple"> <dt><code class="docutils literal notranslate"><span class="pre">get_current()</span> <span class="pre">-></span> <span class="pre">Interpreter</span></code></dt><dd>Returns the <code class="docutils literal notranslate"><span class="pre">Interpreter</span></code> object for the currently executing interpreter.</dd> </dl> </li> <li><dl class="simple"> <dt><code class="docutils literal notranslate"><span class="pre">list_all()</span> <span class="pre">-></span> <span class="pre">list[Interpreter]</span></code></dt><dd>Returns the <code class="docutils literal notranslate"><span class="pre">Interpreter</span></code> object for each existing interpreter, whether it is currently running in any OS threads or not.</dd> </dl> </li> <li><dl class="simple"> <dt><code class="docutils literal notranslate"><span class="pre">create()</span> <span class="pre">-></span> <span class="pre">Interpreter</span></code></dt><dd>Create a new interpreter and return the <code class="docutils literal notranslate"><span class="pre">Interpreter</span></code> object for it. The interpreter doesn’t do anything on its own and is not inherently tied to any OS thread. That only happens when something is actually run in the interpreter (e.g. <code class="docutils literal notranslate"><span class="pre">Interpreter.exec()</span></code>), and only while running. The interpreter may or may not have thread states ready to use, but that is strictly an internal implementation detail.</dd> </dl> </li> </ul> </section> <section id="interpreter-objects"> <h3><a class="toc-backref" href="#interpreter-objects" role="doc-backlink">Interpreter Objects</a></h3> <p>An <code class="docutils literal notranslate"><span class="pre">interpreters.Interpreter</span></code> object that represents the interpreter (<code class="docutils literal notranslate"><span class="pre">PyInterpreterState</span></code>) with the corresponding unique ID. There will only be one object for any given interpreter.</p> <p>If the interpreter was created with <code class="docutils literal notranslate"><span class="pre">interpreters.create()</span></code> then it will be destroyed as soon as all <code class="docutils literal notranslate"><span class="pre">Interpreter</span></code> objects with its ID (across all interpreters) have been deleted.</p> <p><code class="docutils literal notranslate"><span class="pre">Interpreter</span></code> objects may represent other interpreters than those created by <code class="docutils literal notranslate"><span class="pre">interpreters.create()</span></code>. Examples include the main interpreter (created by Python’s runtime initialization) and those created via the C-API, using <code class="docutils literal notranslate"><span class="pre">Py_NewInterpreter()</span></code>. Such <code class="docutils literal notranslate"><span class="pre">Interpreter</span></code> objects will not be able to interact with their corresponding interpreters, e.g. via <code class="docutils literal notranslate"><span class="pre">Interpreter.exec()</span></code> (though we may relax this in the future).</p> <p>Attributes and methods:</p> <ul> <li><dl class="simple"> <dt><code class="docutils literal notranslate"><span class="pre">id</span></code></dt><dd>(read-only) A non-negative <code class="docutils literal notranslate"><span class="pre">int</span></code> that identifies the interpreter that this <code class="docutils literal notranslate"><span class="pre">Interpreter</span></code> instance represents. Conceptually, this is similar to a process ID.</dd> </dl> </li> <li><dl class="simple"> <dt><code class="docutils literal notranslate"><span class="pre">__hash__()</span></code></dt><dd>Returns the hash of the interpreter’s <code class="docutils literal notranslate"><span class="pre">id</span></code>. This is the same as the hash of the ID’s integer value.</dd> </dl> </li> <li><dl> <dt><code class="docutils literal notranslate"><span class="pre">is_running()</span> <span class="pre">-></span> <span class="pre">bool</span></code></dt><dd>Returns <code class="docutils literal notranslate"><span class="pre">True</span></code> if the interpreter is currently executing code in its <code class="docutils literal notranslate"><span class="pre">__main__</span></code> module. This excludes sub-threads.<p>It refers only to if there is an OS thread running a script (code) in the interpreter’s <code class="docutils literal notranslate"><span class="pre">__main__</span></code> module. That basically means whether or not <code class="docutils literal notranslate"><span class="pre">Interpreter.exec()</span></code> is running in some OS thread. Code running in sub-threads is ignored.</p> </dd> </dl> </li> <li><dl> <dt><code class="docutils literal notranslate"><span class="pre">prepare_main(**kwargs)</span></code></dt><dd>Bind one or more objects in the interpreter’s <code class="docutils literal notranslate"><span class="pre">__main__</span></code> module.<p>The keyword argument names will be used as the attribute names. The values will be bound as new objects, though exactly equivalent to the original. Only objects specifically supported for passing between interpreters are allowed. See <a class="reference internal" href="#shareable-objects">Shareable Objects</a>.</p> <p><code class="docutils literal notranslate"><span class="pre">prepare_main()</span></code> is helpful for initializing the globals for an interpreter before running code in it.</p> </dd> </dl> </li> <li><dl> <dt><code class="docutils literal notranslate"><span class="pre">exec(code,</span> <span class="pre">/)</span></code></dt><dd>Execute the given source code in the interpreter (in the current OS thread), using its <code class="docutils literal notranslate"><span class="pre">__main__</span></code> module. It doesn’t return anything.<p>This is essentially equivalent to switching to this interpreter in the current OS thread and then calling the builtin <code class="docutils literal notranslate"><span class="pre">exec()</span></code> using this interpreter’s <code class="docutils literal notranslate"><span class="pre">__main__</span></code> module’s <code class="docutils literal notranslate"><span class="pre">__dict__</span></code> as the globals and locals.</p> <p>The code running in the current OS thread (a different interpreter) is effectively paused until <code class="docutils literal notranslate"><span class="pre">Interpreter.exec()</span></code> finishes. To avoid pausing it, create a new <code class="docutils literal notranslate"><span class="pre">threading.Thread</span></code> and call <code class="docutils literal notranslate"><span class="pre">Interpreter.exec()</span></code> in it (like <code class="docutils literal notranslate"><span class="pre">Interpreter.call_in_thread()</span></code> does).</p> <p><code class="docutils literal notranslate"><span class="pre">Interpreter.exec()</span></code> does not reset the interpreter’s state nor the <code class="docutils literal notranslate"><span class="pre">__main__</span></code> module, neither before nor after, so each successive call picks up where the last one left off. This can be useful for running some code to initialize an interpreter (e.g. with imports) before later performing some repeated task.</p> <p>If there is an uncaught exception, it will be propagated into the calling interpreter as an <code class="docutils literal notranslate"><span class="pre">ExecutionFailed</span></code>. The full error display of the original exception, generated relative to the called interpreter, is preserved on the propagated <code class="docutils literal notranslate"><span class="pre">ExecutionFailed</span></code>. That includes the full traceback, with all the extra info like syntax error details and chained exceptions. If the <code class="docutils literal notranslate"><span class="pre">ExecutionFailed</span></code> is not caught then that full error display will be shown, much like it would be if the propagated exception had been raised in the main interpreter and uncaught. Having the full traceback is particularly useful when debugging.</p> <p>If exception propagation is not desired then an explicit try-except should be used around the <em>code</em> passed to <code class="docutils literal notranslate"><span class="pre">Interpreter.exec()</span></code>. Likewise any error handling that depends on specific information from the exception must use an explicit try-except around the given <em>code</em>, since <code class="docutils literal notranslate"><span class="pre">ExecutionFailed</span></code> will not preserve that information.</p> </dd> </dl> </li> <li><dl> <dt><code class="docutils literal notranslate"><span class="pre">call(callable,</span> <span class="pre">/)</span></code></dt><dd>Call the callable object in the interpreter. The return value is discarded. If the callable raises an exception then it gets propagated as an <code class="docutils literal notranslate"><span class="pre">ExecutionFailed</span></code> exception, in the same way as <code class="docutils literal notranslate"><span class="pre">Interpreter.exec()</span></code>.<p>For now only plain functions are supported and only ones that take no arguments and have no cell vars. Free globals are resolved against the target interpreter’s <code class="docutils literal notranslate"><span class="pre">__main__</span></code> module.</p> <p>In the future, we can add support for arguments, closures, and a broader variety of callables, at least partly via pickle. We can also consider not discarding the return value. The initial restrictions are in place to allow us to get the basic functionality of the module out to users sooner.</p> </dd> </dl> </li> <li><dl> <dt><code class="docutils literal notranslate"><span class="pre">call_in_thread(callable,</span> <span class="pre">/)</span> <span class="pre">-></span> <span class="pre">threading.Thread</span></code></dt><dd>Essentially, apply <code class="docutils literal notranslate"><span class="pre">Interpreter.call()</span></code> in a new thread. Return values are discarded and exceptions are not propagated.<p><code class="docutils literal notranslate"><span class="pre">call_in_thread()</span></code> is roughly equivalent to:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">task</span><span class="p">():</span> <span class="n">interp</span><span class="o">.</span><span class="n">call</span><span class="p">(</span><span class="n">func</span><span class="p">)</span> <span class="n">t</span> <span class="o">=</span> <span class="n">threading</span><span class="o">.</span><span class="n">Thread</span><span class="p">(</span><span class="n">target</span><span class="o">=</span><span class="n">task</span><span class="p">)</span> <span class="n">t</span><span class="o">.</span><span class="n">start</span><span class="p">()</span> </pre></div> </div> </dd> </dl> </li> <li><dl class="simple"> <dt><code class="docutils literal notranslate"><span class="pre">close()</span></code></dt><dd>Destroy the underlying interpreter.</dd> </dl> </li> </ul> </section> <section id="communicating-between-interpreters"> <h3><a class="toc-backref" href="#communicating-between-interpreters" role="doc-backlink">Communicating Between Interpreters</a></h3> <p>The module introduces a basic communication mechanism through special queues.</p> <p>There are <code class="docutils literal notranslate"><span class="pre">interpreters.Queue</span></code> objects, but they only proxy the actual data structure: an unbounded FIFO queue that exists outside any one interpreter. These queues have special accommodations for safely passing object data between interpreters, without violating interpreter isolation. This includes thread-safety.</p> <p>As with other queues in Python, for each “put” the object is added to the back and each “get” pops the next one off the front. Every added object will be popped off in the order it was pushed on.</p> <p>Only objects that are specifically supported for passing between interpreters may be sent through an <code class="docutils literal notranslate"><span class="pre">interpreters.Queue</span></code>. Note that the actual objects aren’t sent, but rather their underlying data. However, the popped object will still be strictly equivalent to the original. See <a class="reference internal" href="#shareable-objects">Shareable Objects</a>.</p> <p>The module defines the following functions:</p> <ul> <li><dl> <dt><code class="docutils literal notranslate"><span class="pre">create_queue(maxsize=0,</span> <span class="pre">*,</span> <span class="pre">syncobj=False)</span> <span class="pre">-></span> <span class="pre">Queue</span></code></dt><dd>Create a new queue. If the maxsize is zero or negative then the queue is unbounded.<p>“syncobj” is used as the default for <code class="docutils literal notranslate"><span class="pre">put()</span></code> and <code class="docutils literal notranslate"><span class="pre">put_nowait()</span></code>.</p> </dd> </dl> </li> </ul> </section> <section id="queue-objects"> <h3><a class="toc-backref" href="#queue-objects" role="doc-backlink">Queue Objects</a></h3> <p><code class="docutils literal notranslate"><span class="pre">interpreters.Queue</span></code> objects act as proxies for the underlying cross-interpreter-safe queues exposed by the <code class="docutils literal notranslate"><span class="pre">interpreters</span></code> module. Each <code class="docutils literal notranslate"><span class="pre">Queue</span></code> object represents the queue with the corresponding unique ID. There will only be one object for any given queue.</p> <p><code class="docutils literal notranslate"><span class="pre">Queue</span></code> implements all the methods of <code class="docutils literal notranslate"><span class="pre">queue.Queue</span></code> except for <code class="docutils literal notranslate"><span class="pre">task_done()</span></code> and <code class="docutils literal notranslate"><span class="pre">join()</span></code>, hence it is similar to <code class="docutils literal notranslate"><span class="pre">asyncio.Queue</span></code> and <code class="docutils literal notranslate"><span class="pre">multiprocessing.Queue</span></code>.</p> <p>Attributes and methods:</p> <ul> <li><dl class="simple"> <dt><code class="docutils literal notranslate"><span class="pre">id</span></code></dt><dd>(read-only) A non-negative <code class="docutils literal notranslate"><span class="pre">int</span></code> that identifies the corresponding cross-interpreter queue. Conceptually, this is similar to the file descriptor used for a pipe.</dd> </dl> </li> <li><dl class="simple"> <dt><code class="docutils literal notranslate"><span class="pre">maxsize</span></code></dt><dd>(read-only) Number of items allowed in the queue. Zero means “unbounded”.</dd> </dl> </li> <li><dl class="simple"> <dt><code class="docutils literal notranslate"><span class="pre">__hash__()</span></code></dt><dd>Return the hash of the queue’s <code class="docutils literal notranslate"><span class="pre">id</span></code>. This is the same as the hash of the ID’s integer value.</dd> </dl> </li> <li><dl> <dt><code class="docutils literal notranslate"><span class="pre">empty()</span></code></dt><dd>Return <code class="docutils literal notranslate"><span class="pre">True</span></code> if the queue is empty, <code class="docutils literal notranslate"><span class="pre">False</span></code> otherwise.<p>This is only a snapshot of the state at the time of the call. Other threads or interpreters may cause this to change.</p> </dd> </dl> </li> <li><dl> <dt><code class="docutils literal notranslate"><span class="pre">full()</span></code></dt><dd>Return <code class="docutils literal notranslate"><span class="pre">True</span></code> if there are <code class="docutils literal notranslate"><span class="pre">maxsize</span></code> items in the queue.<p>If the queue was initialized with <code class="docutils literal notranslate"><span class="pre">maxsize=0</span></code> (the default), then <code class="docutils literal notranslate"><span class="pre">full()</span></code> never returns <code class="docutils literal notranslate"><span class="pre">True</span></code>.</p> <p>This is only a snapshot of the state at the time of the call. Other threads or interpreters may cause this to change.</p> </dd> </dl> </li> <li><dl> <dt><code class="docutils literal notranslate"><span class="pre">qsize()</span></code></dt><dd>Return the number of items in the queue.<p>This is only a snapshot of the state at the time of the call. Other threads or interpreters may cause this to change.</p> </dd> </dl> </li> <li><dl> <dt><code class="docutils literal notranslate"><span class="pre">put(obj,</span> <span class="pre">timeout=None,</span> <span class="pre">*,</span> <span class="pre">syncobj=None)</span></code></dt><dd>Add the object to the queue.<p>If <code class="docutils literal notranslate"><span class="pre">maxsize</span> <span class="pre">></span> <span class="pre">0</span></code> and the queue is full then this blocks until a free slot is available. If <em>timeout</em> is a positive number then it only blocks at least that many seconds and then raises <code class="docutils literal notranslate"><span class="pre">interpreters.QueueFull</span></code>. Otherwise is blocks forever.</p> <p>If “syncobj” is true then the object must be <a class="reference internal" href="#shareable-objects">shareable</a>, which means the object’s data is passed through rather than the object itself. If “syncobj” is false then all objects are supported. However, there are some performance penalties and all objects are copies (e.g. via pickle). Thus mutable objects will never be automatically synchronized between interpreters. If “syncobj” is None (the default) then the queue’s default value is used.</p> <p>If an object is still in the queue, and the interpreter which put it in the queue (i.e. to which it belongs) is destroyed, then the object is immediately removed from the queue. (We may later add an option to replace the removed object in the queue with a sentinel or to raise an exception for the corresponding <code class="docutils literal notranslate"><span class="pre">get()</span></code> call.)</p> </dd> </dl> </li> <li><dl class="simple"> <dt><code class="docutils literal notranslate"><span class="pre">put_nowait(obj,</span> <span class="pre">*,</span> <span class="pre">syncobj=None)</span></code></dt><dd>Like <code class="docutils literal notranslate"><span class="pre">put()</span></code> but effectively with an immediate timeout. Thus if the queue is full, it immediately raises <code class="docutils literal notranslate"><span class="pre">interpreters.QueueFull</span></code>.</dd> </dl> </li> <li><dl class="simple"> <dt><code class="docutils literal notranslate"><span class="pre">get(timeout=None)</span> <span class="pre">-></span> <span class="pre">object</span></code></dt><dd>Pop the next object from the queue and return it. Block while the queue is empty. If a positive <em>timeout</em> is provided and an object hasn’t been added to the queue in that many seconds then raise <code class="docutils literal notranslate"><span class="pre">interpreters.QueueEmpty</span></code>.</dd> </dl> </li> <li><dl class="simple"> <dt><code class="docutils literal notranslate"><span class="pre">get_nowait()</span> <span class="pre">-></span> <span class="pre">object</span></code></dt><dd>Like <code class="docutils literal notranslate"><span class="pre">get()</span></code>, but do not block. If the queue is not empty then return the next item. Otherwise, raise <code class="docutils literal notranslate"><span class="pre">interpreters.QueueEmpty</span></code>.</dd> </dl> </li> </ul> </section> <section id="shareable-objects"> <h3><a class="toc-backref" href="#shareable-objects" role="doc-backlink">Shareable Objects</a></h3> <p><code class="docutils literal notranslate"><span class="pre">Interpreter.prepare_main()</span></code> only works with “shareable” objects. The same goes for <code class="docutils literal notranslate"><span class="pre">interpreters.Queue</span></code> (optionally).</p> <p>A “shareable” object is one which may be passed from one interpreter to another. The object is not necessarily actually directly shared by the interpreters. However, even if it isn’t, the shared object should be treated as though it <em>were</em> shared directly. That’s a strong equivalence guarantee for all shareable objects. (See below.)</p> <p>For some types (builtin singletons), the actual object is shared. For some, the object’s underlying data is actually shared but each interpreter has a distinct object wrapping that data. For all other shareable types, a strict copy or proxy is made such that the corresponding objects continue to match exactly. In cases where the underlying data is complex but must be copied (e.g. <code class="docutils literal notranslate"><span class="pre">tuple</span></code>), the data is serialized as efficiently as possible.</p> <p>Shareable objects must be specifically supported internally by the Python runtime. However, there is no restriction against adding support for more types later.</p> <p>Here’s the initial list of supported objects:</p> <ul class="simple"> <li><code class="docutils literal notranslate"><span class="pre">str</span></code></li> <li><code class="docutils literal notranslate"><span class="pre">bytes</span></code></li> <li><code class="docutils literal notranslate"><span class="pre">int</span></code></li> <li><code class="docutils literal notranslate"><span class="pre">float</span></code></li> <li><code class="docutils literal notranslate"><span class="pre">bool</span></code> (<code class="docutils literal notranslate"><span class="pre">True</span></code>/<code class="docutils literal notranslate"><span class="pre">False</span></code>)</li> <li><code class="docutils literal notranslate"><span class="pre">None</span></code></li> <li><code class="docutils literal notranslate"><span class="pre">tuple</span></code> (only with shareable items)</li> <li><code class="docutils literal notranslate"><span class="pre">interpreters.Queue</span></code></li> <li><code class="docutils literal notranslate"><span class="pre">memoryview</span></code> (underlying buffer actually shared)</li> </ul> <p>Note that the last two on the list, queues and <code class="docutils literal notranslate"><span class="pre">memoryview</span></code>, are technically mutable data types, whereas the rest are not. When any interpreters share mutable data there is always a risk of data races. Cross-interpreter safety, including thread-safety, is a fundamental feature of queues.</p> <p>However, <code class="docutils literal notranslate"><span class="pre">memoryview</span></code> does not have any native accommodations. The user is responsible for managing thread-safety, whether passing a token back and forth through a queue to indicate safety (see <a class="reference internal" href="#synchronization">Synchronization</a>), or by assigning sub-range exclusivity to individual interpreters.</p> <p>Most objects will be shared through queues (<code class="docutils literal notranslate"><span class="pre">interpreters.Queue</span></code>), as interpreters communicate information between each other. Less frequently, objects will be shared through <code class="docutils literal notranslate"><span class="pre">prepare_main()</span></code> to set up an interpreter prior to running code in it. However, <code class="docutils literal notranslate"><span class="pre">prepare_main()</span></code> is the primary way that queues are shared, to provide another interpreter with a means of further communication.</p> <p>Finally, a reminder: for a few types the actual object is shared, whereas for the rest only the underlying data is shared, whether as a copy or through a proxy. Regardless, it always preserves the strong equivalence guarantee of “shareable” objects.</p> <p>The guarantee is that a shared object in one interpreter is strictly equivalent to the corresponding object in the other interpreter. In other words, the two objects will be indistinguishable from each other. The shared object should be treated as though the original had been shared directly, whether or not it actually was. That’s a slightly different and stronger promise than just equality.</p> <p>The guarantee is especially important for mutable objects, like <code class="docutils literal notranslate"><span class="pre">Interpreters.Queue</span></code> and <code class="docutils literal notranslate"><span class="pre">memoryview</span></code>. Mutating the object in one interpreter will always be reflected immediately in every other interpreter sharing the object.</p> </section> <section id="synchronization"> <h3><a class="toc-backref" href="#synchronization" role="doc-backlink">Synchronization</a></h3> <p>There are situations where two interpreters should be synchronized. That may involve sharing a resource, worker management, or preserving sequential consistency.</p> <p>In threaded programming the typical synchronization primitives are types like mutexes. The <code class="docutils literal notranslate"><span class="pre">threading</span></code> module exposes several. However, interpreters cannot share objects which means they cannot share <code class="docutils literal notranslate"><span class="pre">threading.Lock</span></code> objects.</p> <p>The <code class="docutils literal notranslate"><span class="pre">interpreters</span></code> module does not provide any such dedicated synchronization primitives. Instead, <code class="docutils literal notranslate"><span class="pre">interpreters.Queue</span></code> objects provide everything one might need.</p> <p>For example, if there’s a shared resource that needs managed access then a queue may be used to manage it, where the interpreters pass an object around to indicate who can use the resource:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span><span class="w"> </span><span class="nn">interpreters</span> <span class="kn">from</span><span class="w"> </span><span class="nn">mymodule</span><span class="w"> </span><span class="kn">import</span> <span class="n">load_big_data</span><span class="p">,</span> <span class="n">check_data</span> <span class="n">numworkers</span> <span class="o">=</span> <span class="mi">10</span> <span class="n">control</span> <span class="o">=</span> <span class="n">interpreters</span><span class="o">.</span><span class="n">create_queue</span><span class="p">()</span> <span class="n">data</span> <span class="o">=</span> <span class="nb">memoryview</span><span class="p">(</span><span class="n">load_big_data</span><span class="p">())</span> <span class="k">def</span><span class="w"> </span><span class="nf">worker</span><span class="p">():</span> <span class="n">interp</span> <span class="o">=</span> <span class="n">interpreters</span><span class="o">.</span><span class="n">create</span><span class="p">()</span> <span class="n">interp</span><span class="o">.</span><span class="n">prepare_main</span><span class="p">(</span><span class="n">control</span><span class="o">=</span><span class="n">control</span><span class="p">,</span> <span class="n">data</span><span class="o">=</span><span class="n">data</span><span class="p">)</span> <span class="n">interp</span><span class="o">.</span><span class="n">exec</span><span class="p">(</span><span class="s2">"""if True:</span> <span class="s2"> from mymodule import edit_data</span> <span class="s2"> while True:</span> <span class="s2"> token = control.get()</span> <span class="s2"> edit_data(data)</span> <span class="s2"> control.put(token)</span> <span class="s2"> """</span><span class="p">)</span> <span class="n">threads</span> <span class="o">=</span> <span class="p">[</span><span class="n">threading</span><span class="o">.</span><span class="n">Thread</span><span class="p">(</span><span class="n">target</span><span class="o">=</span><span class="n">worker</span><span class="p">)</span> <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">numworkers</span><span class="p">)]</span> <span class="k">for</span> <span class="n">t</span> <span class="ow">in</span> <span class="n">threads</span><span class="p">:</span> <span class="n">t</span><span class="o">.</span><span class="n">start</span><span class="p">()</span> <span class="n">token</span> <span class="o">=</span> <span class="s1">'football'</span> <span class="n">control</span><span class="o">.</span><span class="n">put</span><span class="p">(</span><span class="n">token</span><span class="p">)</span> <span class="k">while</span> <span class="kc">True</span><span class="p">:</span> <span class="n">control</span><span class="o">.</span><span class="n">get</span><span class="p">()</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">check_data</span><span class="p">(</span><span class="n">data</span><span class="p">):</span> <span class="k">break</span> <span class="n">control</span><span class="o">.</span><span class="n">put</span><span class="p">(</span><span class="n">token</span><span class="p">)</span> </pre></div> </div> </section> <section id="exceptions"> <h3><a class="toc-backref" href="#exceptions" role="doc-backlink">Exceptions</a></h3> <ul> <li><dl> <dt><code class="docutils literal notranslate"><span class="pre">InterpreterError</span></code></dt><dd>Indicates that some interpreter-related failure occurred.<p>This exception is a subclass of <code class="docutils literal notranslate"><span class="pre">Exception</span></code>.</p> </dd> </dl> </li> <li><dl> <dt><code class="docutils literal notranslate"><span class="pre">InterpreterNotFoundError</span></code></dt><dd>Raised from <code class="docutils literal notranslate"><span class="pre">Interpreter</span></code> methods after the underlying interpreter has been destroyed, e.g. via the C-API.<p>This exception is a subclass of <code class="docutils literal notranslate"><span class="pre">InterpreterError</span></code>.</p> </dd> </dl> </li> <li><dl> <dt><code class="docutils literal notranslate"><span class="pre">ExecutionFailed</span></code></dt><dd>Raised from <code class="docutils literal notranslate"><span class="pre">Interpreter.exec()</span></code> and <code class="docutils literal notranslate"><span class="pre">Interpreter.call()</span></code> when there’s an uncaught exception. The error display for this exception includes the traceback of the uncaught exception, which gets shown after the normal error display, much like happens for <code class="docutils literal notranslate"><span class="pre">ExceptionGroup</span></code>.<p>Attributes:</p> <ul class="simple"> <li><code class="docutils literal notranslate"><span class="pre">type</span></code> - a representation of the original exception’s class, with <code class="docutils literal notranslate"><span class="pre">__name__</span></code>, <code class="docutils literal notranslate"><span class="pre">__module__</span></code>, and <code class="docutils literal notranslate"><span class="pre">__qualname__</span></code> attrs.</li> <li><code class="docutils literal notranslate"><span class="pre">msg</span></code> - <code class="docutils literal notranslate"><span class="pre">str(exc)</span></code> of the original exception</li> <li><code class="docutils literal notranslate"><span class="pre">snapshot</span></code> - a <code class="docutils literal notranslate"><span class="pre">traceback.TracebackException</span></code> object for the original exception</li> </ul> <p>This exception is a subclass of <code class="docutils literal notranslate"><span class="pre">InterpreterError</span></code>.</p> </dd> </dl> </li> <li><dl> <dt><code class="docutils literal notranslate"><span class="pre">QueueError</span></code></dt><dd>Indicates that some queue-related failure occurred.<p>This exception is a subclass of <code class="docutils literal notranslate"><span class="pre">Exception</span></code>.</p> </dd> </dl> </li> <li><dl> <dt><code class="docutils literal notranslate"><span class="pre">QueueNotFoundError</span></code></dt><dd>Raised from <code class="docutils literal notranslate"><span class="pre">interpreters.Queue</span></code> methods after the underlying queue has been destroyed.<p>This exception is a subclass of <code class="docutils literal notranslate"><span class="pre">QueueError</span></code>.</p> </dd> </dl> </li> <li><dl> <dt><code class="docutils literal notranslate"><span class="pre">QueueEmpty</span></code></dt><dd>Raised from <code class="docutils literal notranslate"><span class="pre">Queue.get()</span></code> (or <code class="docutils literal notranslate"><span class="pre">get_nowait()</span></code> with no default) when the queue is empty.<p>This exception is a subclass of both <code class="docutils literal notranslate"><span class="pre">QueueError</span></code> and the stdlib <code class="docutils literal notranslate"><span class="pre">queue.Empty</span></code>.</p> </dd> </dl> </li> <li><dl> <dt><code class="docutils literal notranslate"><span class="pre">QueueFull</span></code></dt><dd>Raised from <code class="docutils literal notranslate"><span class="pre">Queue.put()</span></code> (with a timeout) or <code class="docutils literal notranslate"><span class="pre">put_nowait()</span></code> when the queue is already at its max size.<p>This exception is a subclass of both <code class="docutils literal notranslate"><span class="pre">QueueError</span></code> and the stdlib <code class="docutils literal notranslate"><span class="pre">queue.Empty</span></code>.</p> </dd> </dl> </li> </ul> </section> <section id="interpreterpoolexecutor"> <h3><a class="toc-backref" href="#interpreterpoolexecutor" role="doc-backlink">InterpreterPoolExecutor</a></h3> <p>Along with the new <code class="docutils literal notranslate"><span class="pre">interpreters</span></code> module, there will be a new <code class="docutils literal notranslate"><span class="pre">concurrent.futures.InterpreterPoolExecutor</span></code>. It will be a derivative of <code class="docutils literal notranslate"><span class="pre">ThreadPoolExecutor</span></code>, where each worker executes in its own thread, but each with its own subinterpreter.</p> <p>Like the other executors, <code class="docutils literal notranslate"><span class="pre">InterpreterPoolExecutor</span></code> will support callables for tasks, and for the initializer. Also like the other executors, the arguments in both cases will be mostly unrestricted. The callables and arguments will typically be serialized when sent to a worker’s interpreter, e.g. with pickle, like how the <code class="docutils literal notranslate"><span class="pre">ProcessPoolExecutor</span></code> works. This contrasts with <code class="docutils literal notranslate"><span class="pre">Interpreter.call()</span></code>, which will (at least initially) be much more restricted.</p> <p>Communication between workers, or between the executor (or generally its interpreter) and the workers, may still be done through <code class="docutils literal notranslate"><span class="pre">interpreters.Queue</span></code> objects, set with the initializer.</p> </section> <section id="sys-implementation-supports-isolated-interpreters"> <h3><a class="toc-backref" href="#sys-implementation-supports-isolated-interpreters" role="doc-backlink">sys.implementation.supports_isolated_interpreters</a></h3> <p>Python implementations are not required to support subinterpreters, though most major ones do. If an implementation does support them then <code class="docutils literal notranslate"><span class="pre">sys.implementation.supports_isolated_interpreters</span></code> will be set to <code class="docutils literal notranslate"><span class="pre">True</span></code>. Otherwise it will be <code class="docutils literal notranslate"><span class="pre">False</span></code>. If the feature is not supported then importing the <code class="docutils literal notranslate"><span class="pre">interpreters</span></code> module will raise an <code class="docutils literal notranslate"><span class="pre">ImportError</span></code>.</p> </section> <section id="examples"> <h3><a class="toc-backref" href="#examples" role="doc-backlink">Examples</a></h3> <p>The following examples demonstrate practical cases where multiple interpreters may be useful.</p> <p>Example 1:</p> <p>There’s a stream of requests coming in that will be handled via workers in sub-threads.</p> <ul class="simple"> <li>each worker thread has its own interpreter</li> <li>there’s one queue to send tasks to workers and another queue to return results</li> <li>the results are handled in a dedicated thread</li> <li>each worker keeps going until it receives a “stop” sentinel (<code class="docutils literal notranslate"><span class="pre">None</span></code>)</li> <li>the results handler keeps going until all workers have stopped</li> </ul> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span><span class="w"> </span><span class="nn">interpreters</span> <span class="kn">from</span><span class="w"> </span><span class="nn">mymodule</span><span class="w"> </span><span class="kn">import</span> <span class="n">iter_requests</span><span class="p">,</span> <span class="n">handle_result</span> <span class="n">tasks</span> <span class="o">=</span> <span class="n">interpreters</span><span class="o">.</span><span class="n">create_queue</span><span class="p">()</span> <span class="n">results</span> <span class="o">=</span> <span class="n">interpreters</span><span class="o">.</span><span class="n">create_queue</span><span class="p">()</span> <span class="n">numworkers</span> <span class="o">=</span> <span class="mi">20</span> <span class="n">threads</span> <span class="o">=</span> <span class="p">[]</span> <span class="k">def</span><span class="w"> </span><span class="nf">results_handler</span><span class="p">():</span> <span class="n">running</span> <span class="o">=</span> <span class="n">numworkers</span> <span class="k">while</span> <span class="n">running</span><span class="p">:</span> <span class="k">try</span><span class="p">:</span> <span class="n">res</span> <span class="o">=</span> <span class="n">results</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">timeout</span><span class="o">=</span><span class="mf">0.1</span><span class="p">)</span> <span class="k">except</span> <span class="n">interpreters</span><span class="o">.</span><span class="n">QueueEmpty</span><span class="p">:</span> <span class="c1"># No workers have finished a request since last time.</span> <span class="k">pass</span> <span class="k">else</span><span class="p">:</span> <span class="k">if</span> <span class="n">res</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span> <span class="c1"># A worker has stopped.</span> <span class="n">running</span> <span class="o">-=</span> <span class="mi">1</span> <span class="k">else</span><span class="p">:</span> <span class="n">handle_result</span><span class="p">(</span><span class="n">res</span><span class="p">)</span> <span class="n">empty</span> <span class="o">=</span> <span class="nb">object</span><span class="p">()</span> <span class="k">assert</span> <span class="n">results</span><span class="o">.</span><span class="n">get_nowait</span><span class="p">(</span><span class="n">empty</span><span class="p">)</span> <span class="ow">is</span> <span class="n">empty</span> <span class="n">threads</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">threading</span><span class="o">.</span><span class="n">Thread</span><span class="p">(</span><span class="n">target</span><span class="o">=</span><span class="n">results_handler</span><span class="p">))</span> <span class="k">def</span><span class="w"> </span><span class="nf">worker</span><span class="p">():</span> <span class="n">interp</span> <span class="o">=</span> <span class="n">interpreters</span><span class="o">.</span><span class="n">create</span><span class="p">()</span> <span class="n">interp</span><span class="o">.</span><span class="n">prepare_main</span><span class="p">(</span><span class="n">tasks</span><span class="o">=</span><span class="n">tasks</span><span class="p">,</span> <span class="n">results</span><span class="o">=</span><span class="n">results</span><span class="p">)</span> <span class="n">interp</span><span class="o">.</span><span class="n">exec</span><span class="p">(</span><span class="s2">"""if True:</span> <span class="s2"> from mymodule import handle_request, capture_exception</span> <span class="s2"> while True:</span> <span class="s2"> req = tasks.get()</span> <span class="s2"> if req is None:</span> <span class="s2"> # Stop!</span> <span class="s2"> break</span> <span class="s2"> try:</span> <span class="s2"> res = handle_request(req)</span> <span class="s2"> except Exception as exc:</span> <span class="s2"> res = capture_exception(exc)</span> <span class="s2"> results.put(res)</span> <span class="s2"> # Notify the results handler.</span> <span class="s2"> results.put(None)</span> <span class="s2"> """</span><span class="p">)</span> <span class="n">threads</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="n">threading</span><span class="o">.</span><span class="n">Thread</span><span class="p">(</span><span class="n">target</span><span class="o">=</span><span class="n">worker</span><span class="p">)</span> <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">numworkers</span><span class="p">))</span> <span class="k">for</span> <span class="n">t</span> <span class="ow">in</span> <span class="n">threads</span><span class="p">:</span> <span class="n">t</span><span class="o">.</span><span class="n">start</span><span class="p">()</span> <span class="k">for</span> <span class="n">req</span> <span class="ow">in</span> <span class="n">iter_requests</span><span class="p">():</span> <span class="n">tasks</span><span class="o">.</span><span class="n">put</span><span class="p">(</span><span class="n">req</span><span class="p">)</span> <span class="c1"># Send the "stop" signal.</span> <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">numworkers</span><span class="p">):</span> <span class="n">tasks</span><span class="o">.</span><span class="n">put</span><span class="p">(</span><span class="kc">None</span><span class="p">)</span> <span class="k">for</span> <span class="n">t</span> <span class="ow">in</span> <span class="n">threads</span><span class="p">:</span> <span class="n">t</span><span class="o">.</span><span class="n">join</span><span class="p">()</span> </pre></div> </div> <p>Example 2:</p> <p>This case is similar to the last as there are a bunch of workers in sub-threads. However, this time the code is chunking up a big array of data, where each worker processes one chunk at a time. Copying that data to each interpreter would be exceptionally inefficient, so the code takes advantage of directly sharing <code class="docutils literal notranslate"><span class="pre">memoryview</span></code> buffers.</p> <ul class="simple"> <li>all the interpreters share the buffer of the source array</li> <li>each one writes its results to a second shared buffer</li> <li>there’s use a queue to send tasks to workers</li> <li>only one worker will ever read any given index in the source array</li> <li>only one worker will ever write to any given index in the results (this is how it ensures thread-safety)</li> </ul> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span><span class="w"> </span><span class="nn">interpreters</span> <span class="kn">import</span><span class="w"> </span><span class="nn">queue</span> <span class="kn">from</span><span class="w"> </span><span class="nn">mymodule</span><span class="w"> </span><span class="kn">import</span> <span class="n">read_large_data_set</span><span class="p">,</span> <span class="n">use_results</span> <span class="n">numworkers</span> <span class="o">=</span> <span class="mi">3</span> <span class="n">data</span><span class="p">,</span> <span class="n">chunksize</span> <span class="o">=</span> <span class="n">read_large_data_set</span><span class="p">()</span> <span class="n">buf</span> <span class="o">=</span> <span class="nb">memoryview</span><span class="p">(</span><span class="n">data</span><span class="p">)</span> <span class="n">numchunks</span> <span class="o">=</span> <span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">buf</span><span class="p">)</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span> <span class="o">/</span> <span class="n">chunksize</span> <span class="n">results</span> <span class="o">=</span> <span class="nb">memoryview</span><span class="p">(</span><span class="sa">b</span><span class="s1">'</span><span class="se">\0</span><span class="s1">'</span> <span class="o">*</span> <span class="n">numchunks</span><span class="p">)</span> <span class="n">tasks</span> <span class="o">=</span> <span class="n">interpreters</span><span class="o">.</span><span class="n">create_queue</span><span class="p">()</span> <span class="k">def</span><span class="w"> </span><span class="nf">worker</span><span class="p">(</span><span class="nb">id</span><span class="p">):</span> <span class="n">interp</span> <span class="o">=</span> <span class="n">interpreters</span><span class="o">.</span><span class="n">create</span><span class="p">()</span> <span class="n">interp</span><span class="o">.</span><span class="n">prepare_main</span><span class="p">(</span><span class="n">data</span><span class="o">=</span><span class="n">buf</span><span class="p">,</span> <span class="n">results</span><span class="o">=</span><span class="n">results</span><span class="p">,</span> <span class="n">tasks</span><span class="o">=</span><span class="n">tasks</span><span class="p">)</span> <span class="n">interp</span><span class="o">.</span><span class="n">exec</span><span class="p">(</span><span class="s2">"""if True:</span> <span class="s2"> from mymodule import reduce_chunk</span> <span class="s2"> while True:</span> <span class="s2"> req = tasks.get()</span> <span class="s2"> if res is None:</span> <span class="s2"> # Stop!</span> <span class="s2"> break</span> <span class="s2"> resindex, start, end = req</span> <span class="s2"> chunk = data[start: end]</span> <span class="s2"> res = reduce_chunk(chunk)</span> <span class="s2"> results[resindex] = res</span> <span class="s2"> """</span><span class="p">)</span> <span class="n">threads</span> <span class="o">=</span> <span class="p">[</span><span class="n">threading</span><span class="o">.</span><span class="n">Thread</span><span class="p">(</span><span class="n">target</span><span class="o">=</span><span class="n">worker</span><span class="p">)</span> <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">numworkers</span><span class="p">)]</span> <span class="k">for</span> <span class="n">t</span> <span class="ow">in</span> <span class="n">threads</span><span class="p">:</span> <span class="n">t</span><span class="o">.</span><span class="n">start</span><span class="p">()</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">numchunks</span><span class="p">):</span> <span class="c1"># Assume there's at least one worker running still.</span> <span class="n">start</span> <span class="o">=</span> <span class="n">i</span> <span class="o">*</span> <span class="n">chunksize</span> <span class="n">end</span> <span class="o">=</span> <span class="n">start</span> <span class="o">+</span> <span class="n">chunksize</span> <span class="k">if</span> <span class="n">end</span> <span class="o">></span> <span class="nb">len</span><span class="p">(</span><span class="n">buf</span><span class="p">):</span> <span class="n">end</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">buf</span><span class="p">)</span> <span class="n">tasks</span><span class="o">.</span><span class="n">put</span><span class="p">((</span><span class="n">start</span><span class="p">,</span> <span class="n">end</span><span class="p">,</span> <span class="n">i</span><span class="p">))</span> <span class="c1"># Send the "stop" signal.</span> <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">numworkers</span><span class="p">):</span> <span class="n">tasks</span><span class="o">.</span><span class="n">put</span><span class="p">(</span><span class="kc">None</span><span class="p">)</span> <span class="k">for</span> <span class="n">t</span> <span class="ow">in</span> <span class="n">threads</span><span class="p">:</span> <span class="n">t</span><span class="o">.</span><span class="n">join</span><span class="p">()</span> <span class="n">use_results</span><span class="p">(</span><span class="n">results</span><span class="p">)</span> </pre></div> </div> </section> </section> <section id="rationale"> <h2><a class="toc-backref" href="#rationale" role="doc-backlink">Rationale</a></h2> <section id="a-minimal-api"> <h3><a class="toc-backref" href="#a-minimal-api" role="doc-backlink">A Minimal API</a></h3> <p>Since the core dev team has no real experience with how users will make use of multiple interpreters in Python code, this proposal purposefully keeps the initial API as lean and minimal as possible. The objective is to provide a well-considered foundation on which further (more advanced) functionality may be added later, as appropriate.</p> <p>That said, the proposed design incorporates lessons learned from existing use of subinterpreters by the community, from existing stdlib modules, and from other programming languages. It also factors in experience from using subinterpreters in the CPython test suite and using them in <a class="reference external" href="https://github.com/ericsnowcurrently/concurrency-benchmarks">concurrency benchmarks</a>.</p> </section> <section id="create-create-queue"> <h3><a class="toc-backref" href="#create-create-queue" role="doc-backlink">create(), create_queue()</a></h3> <p>Typically, users call a type to create instances of the type, at which point the object’s resources get provisioned. The <code class="docutils literal notranslate"><span class="pre">interpreters</span></code> module takes a different approach, where users must call <code class="docutils literal notranslate"><span class="pre">create()</span></code> to get a new interpreter or <code class="docutils literal notranslate"><span class="pre">create_queue()</span></code> for a new queue. Calling <code class="docutils literal notranslate"><span class="pre">interpreters.Interpreter()</span></code> directly only returns a wrapper around an existing interpreters (likewise for <code class="docutils literal notranslate"><span class="pre">interpreters.Queue()</span></code>).</p> <p>This is because interpreters (and queues) are special resources. They exist globally in the process and are not managed/owned by the current interpreter. Thus the <code class="docutils literal notranslate"><span class="pre">interpreters</span></code> module makes creating an interpreter (or queue) a visibly distinct operation from creating an instance of <code class="docutils literal notranslate"><span class="pre">interpreters.Interpreter</span></code> (or <code class="docutils literal notranslate"><span class="pre">interpreters.Queue</span></code>).</p> </section> <section id="interpreter-prepare-main-sets-multiple-variables"> <h3><a class="toc-backref" href="#interpreter-prepare-main-sets-multiple-variables" role="doc-backlink">Interpreter.prepare_main() Sets Multiple Variables</a></h3> <p><code class="docutils literal notranslate"><span class="pre">prepare_main()</span></code> may be seen as a setter function of sorts. It supports setting multiple names at once, e.g. <code class="docutils literal notranslate"><span class="pre">interp.prepare_main(spam=1,</span> <span class="pre">eggs=2)</span></code>, whereas most setters set one item at a time. The main reason is for efficiency.</p> <p>To set a value in the interpreter’s <code class="docutils literal notranslate"><span class="pre">__main__.__dict__</span></code>, the implementation must first switch the OS thread to the identified interpreter, which involves some non-negligible overhead. After setting the value it must switch back. Furthermore, there is some additional overhead to the mechanism by which it passes objects between interpreters, which can be reduced in aggregate if multiple values are set at once.</p> <p>Therefore, <code class="docutils literal notranslate"><span class="pre">prepare_main()</span></code> supports setting multiple values at once.</p> </section> <section id="propagating-exceptions"> <h3><a class="toc-backref" href="#propagating-exceptions" role="doc-backlink">Propagating Exceptions</a></h3> <p>An uncaught exception from a subinterpreter, via <code class="docutils literal notranslate"><span class="pre">Interpreter.exec()</span></code>, could either be (effectively) ignored, like <code class="docutils literal notranslate"><span class="pre">threading.Thread()</span></code> does, or propagated, like the builtin <code class="docutils literal notranslate"><span class="pre">exec()</span></code> does. Since <code class="docutils literal notranslate"><span class="pre">Interpreter.exec()</span></code> is a synchronous operation, like the builtin <code class="docutils literal notranslate"><span class="pre">exec()</span></code>, uncaught exceptions are propagated.</p> <p>However, such exceptions are not raised directly. That’s because interpreters are isolated from each other and must not share objects, including exceptions. That could be addressed by raising a surrogate of the exception, whether a summary, a copy, or a proxy that wraps it. Any of those could preserve the traceback, which is useful for debugging. The <code class="docutils literal notranslate"><span class="pre">ExecutionFailed</span></code> that gets raised is such a surrogate.</p> <p>There’s another concern to consider. If a propagated exception isn’t immediately caught, it will bubble up through the call stack until caught (or not). In the case that code somewhere else may catch it, it is helpful to identify that the exception came from a subinterpreter (i.e. a “remote” source), rather than from the current interpreter. That’s why <code class="docutils literal notranslate"><span class="pre">Interpreter.exec()</span></code> raises <code class="docutils literal notranslate"><span class="pre">ExecutionFailed</span></code> and why it is a plain <code class="docutils literal notranslate"><span class="pre">Exception</span></code>, rather than a copy or proxy with a class that matches the original exception. For example, an uncaught <code class="docutils literal notranslate"><span class="pre">ValueError</span></code> from a subinterpreter would never get caught in a later <code class="docutils literal notranslate"><span class="pre">try:</span> <span class="pre">...</span> <span class="pre">except</span> <span class="pre">ValueError:</span> <span class="pre">...</span></code>. Instead, <code class="docutils literal notranslate"><span class="pre">ExecutionFailed</span></code> must be handled directly.</p> <p>In contrast, exceptions propagated from <code class="docutils literal notranslate"><span class="pre">Interpreter.call()</span></code> do not involve <code class="docutils literal notranslate"><span class="pre">ExecutionFailed</span></code> but are raised directly, as though originating in the calling interpreter. This is because <code class="docutils literal notranslate"><span class="pre">Interpreter.call()</span></code> is a higher level method that uses pickle to support objects that can’t normally be passed between interpreters.</p> </section> <section id="limited-object-sharing"> <h3><a class="toc-backref" href="#limited-object-sharing" role="doc-backlink">Limited Object Sharing</a></h3> <p>As noted in <a class="reference internal" href="#interpreter-isolation">Interpreter Isolation</a>, only a small number of builtin objects may be truly shared between interpreters. In all other cases objects can only be shared indirectly, through copies or proxies.</p> <p>The set of objects that are shareable as copies through queues (and <code class="docutils literal notranslate"><span class="pre">Interpreter.prepare_main()</span></code>) is limited for the sake of efficiency.</p> <p>Supporting sharing of <em>all</em> objects is possible (via pickle) but not part of this proposal. For one thing, it’s helpful to know in those cases that only an efficient implementation is being used. Furthermore, in those cases supporting mutable objects via pickling would violate the guarantee that “shared” objects be equivalent (and stay that way).</p> </section> <section id="objects-vs-id-proxies"> <h3><a class="toc-backref" href="#objects-vs-id-proxies" role="doc-backlink">Objects vs. ID Proxies</a></h3> <p>For both interpreters and queues, the low-level module makes use of proxy objects that expose the underlying state by their corresponding process-global IDs. In both cases the state is likewise process-global and will be used by multiple interpreters. Thus they aren’t suitable to be implemented as <code class="docutils literal notranslate"><span class="pre">PyObject</span></code>, which is only really an option for interpreter-specific data. That’s why the <code class="docutils literal notranslate"><span class="pre">interpreters</span></code> module instead provides objects that are weakly associated through the ID.</p> </section> </section> <section id="rejected-ideas"> <h2><a class="toc-backref" href="#rejected-ideas" role="doc-backlink">Rejected Ideas</a></h2> <p>See <a class="pep reference internal" href="../pep-0554/#rejected-ideas" title="PEP 554 – Multiple Interpreters in the Stdlib § Rejected Ideas">PEP 554</a>.</p> </section> <section id="copyright"> <h2><a class="toc-backref" href="#copyright" role="doc-backlink">Copyright</a></h2> <p>This document is placed in the public domain or under the CC0-1.0-Universal license, whichever is more permissive.</p> </section> </section> <hr class="docutils" /> <p>Source: <a class="reference external" href="https://github.com/python/peps/blob/main/peps/pep-0734.rst">https://github.com/python/peps/blob/main/peps/pep-0734.rst</a></p> <p>Last modified: <a class="reference external" href="https://github.com/python/peps/commits/main/peps/pep-0734.rst">2024-04-10 21:49:06 GMT</a></p> </article> <nav id="pep-sidebar"> <h2>Contents</h2> <ul> <li><a class="reference internal" href="#abstract">Abstract</a></li> <li><a class="reference internal" href="#introduction">Introduction</a><ul> <li><a class="reference internal" href="#threads-and-thread-states">Threads and Thread States</a></li> <li><a class="reference internal" href="#interpreter-states">Interpreter States</a></li> <li><a class="reference internal" href="#interpreters-and-threads">Interpreters and Threads</a></li> <li><a class="reference internal" href="#the-main-interpreter">The “Main” Interpreter</a></li> <li><a class="reference internal" href="#interpreter-isolation">Interpreter Isolation</a></li> <li><a class="reference internal" href="#existing-execution-components">Existing Execution Components</a><ul> <li><a class="reference internal" href="#builtins-exec">builtins.exec()</a></li> <li><a class="reference internal" href="#command-line">Command-line</a></li> <li><a class="reference internal" href="#threading-thread">threading.Thread</a></li> </ul> </li> </ul> </li> <li><a class="reference internal" href="#motivation">Motivation</a></li> <li><a class="reference internal" href="#specification">Specification</a><ul> <li><a class="reference internal" href="#using-interpreters">Using Interpreters</a></li> <li><a class="reference internal" href="#interpreter-objects">Interpreter Objects</a></li> <li><a class="reference internal" href="#communicating-between-interpreters">Communicating Between Interpreters</a></li> <li><a class="reference internal" href="#queue-objects">Queue Objects</a></li> <li><a class="reference internal" href="#shareable-objects">Shareable Objects</a></li> <li><a class="reference internal" href="#synchronization">Synchronization</a></li> <li><a class="reference internal" href="#exceptions">Exceptions</a></li> <li><a class="reference internal" href="#interpreterpoolexecutor">InterpreterPoolExecutor</a></li> <li><a class="reference internal" href="#sys-implementation-supports-isolated-interpreters">sys.implementation.supports_isolated_interpreters</a></li> <li><a class="reference internal" href="#examples">Examples</a></li> </ul> </li> <li><a class="reference internal" href="#rationale">Rationale</a><ul> <li><a class="reference internal" href="#a-minimal-api">A Minimal API</a></li> <li><a class="reference internal" href="#create-create-queue">create(), create_queue()</a></li> <li><a class="reference internal" href="#interpreter-prepare-main-sets-multiple-variables">Interpreter.prepare_main() Sets Multiple Variables</a></li> <li><a class="reference internal" href="#propagating-exceptions">Propagating Exceptions</a></li> <li><a class="reference internal" href="#limited-object-sharing">Limited Object Sharing</a></li> <li><a class="reference internal" href="#objects-vs-id-proxies">Objects vs. ID Proxies</a></li> </ul> </li> <li><a class="reference internal" href="#rejected-ideas">Rejected Ideas</a></li> <li><a class="reference internal" href="#copyright">Copyright</a></li> </ul> <br> <a id="source" href="https://github.com/python/peps/blob/main/peps/pep-0734.rst">Page Source (GitHub)</a> </nav> </section> <script src="../_static/colour_scheme.js"></script> <script src="../_static/wrap_tables.js"></script> <script src="../_static/sticky_banner.js"></script> </body> </html>