1"""Subinterpreters High Level Module.""" 2 3import threading 4import weakref 5import _interpreters 6 7# aliases: 8from _interpreters import ( 9 InterpreterError, InterpreterNotFoundError, NotShareableError, 10 is_shareable, 11) 12 13 14__all__ = [ 15 'get_current', 'get_main', 'create', 'list_all', 'is_shareable', 16 'Interpreter', 17 'InterpreterError', 'InterpreterNotFoundError', 'ExecutionFailed', 18 'NotShareableError', 19 'create_queue', 'Queue', 'QueueEmpty', 'QueueFull', 20] 21 22 23_queuemod = None 24 25def __getattr__(name): 26 if name in ('Queue', 'QueueEmpty', 'QueueFull', 'create_queue'): 27 global create_queue, Queue, QueueEmpty, QueueFull 28 ns = globals() 29 from .queues import ( 30 create as create_queue, 31 Queue, QueueEmpty, QueueFull, 32 ) 33 return ns[name] 34 else: 35 raise AttributeError(name) 36 37 38_EXEC_FAILURE_STR = """ 39{superstr} 40 41Uncaught in the interpreter: 42 43{formatted} 44""".strip() 45 46class ExecutionFailed(InterpreterError): 47 """An unhandled exception happened during execution. 48 49 This is raised from Interpreter.exec() and Interpreter.call(). 50 """ 51 52 def __init__(self, excinfo): 53 msg = excinfo.formatted 54 if not msg: 55 if excinfo.type and excinfo.msg: 56 msg = f'{excinfo.type.__name__}: {excinfo.msg}' 57 else: 58 msg = excinfo.type.__name__ or excinfo.msg 59 super().__init__(msg) 60 self.excinfo = excinfo 61 62 def __str__(self): 63 try: 64 formatted = self.excinfo.errdisplay 65 except Exception: 66 return super().__str__() 67 else: 68 return _EXEC_FAILURE_STR.format( 69 superstr=super().__str__(), 70 formatted=formatted, 71 ) 72 73 74def create(): 75 """Return a new (idle) Python interpreter.""" 76 id = _interpreters.create(reqrefs=True) 77 return Interpreter(id, _ownsref=True) 78 79 80def list_all(): 81 """Return all existing interpreters.""" 82 return [Interpreter(id, _whence=whence) 83 for id, whence in _interpreters.list_all(require_ready=True)] 84 85 86def get_current(): 87 """Return the currently running interpreter.""" 88 id, whence = _interpreters.get_current() 89 return Interpreter(id, _whence=whence) 90 91 92def get_main(): 93 """Return the main interpreter.""" 94 id, whence = _interpreters.get_main() 95 assert whence == _interpreters.WHENCE_RUNTIME, repr(whence) 96 return Interpreter(id, _whence=whence) 97 98 99_known = weakref.WeakValueDictionary() 100 101class Interpreter: 102 """A single Python interpreter. 103 104 Attributes: 105 106 "id" - the unique process-global ID number for the interpreter 107 "whence" - indicates where the interpreter was created 108 109 If the interpreter wasn't created by this module 110 then any method that modifies the interpreter will fail, 111 i.e. .close(), .prepare_main(), .exec(), and .call() 112 """ 113 114 _WHENCE_TO_STR = { 115 _interpreters.WHENCE_UNKNOWN: 'unknown', 116 _interpreters.WHENCE_RUNTIME: 'runtime init', 117 _interpreters.WHENCE_LEGACY_CAPI: 'legacy C-API', 118 _interpreters.WHENCE_CAPI: 'C-API', 119 _interpreters.WHENCE_XI: 'cross-interpreter C-API', 120 _interpreters.WHENCE_STDLIB: '_interpreters module', 121 } 122 123 def __new__(cls, id, /, _whence=None, _ownsref=None): 124 # There is only one instance for any given ID. 125 if not isinstance(id, int): 126 raise TypeError(f'id must be an int, got {id!r}') 127 id = int(id) 128 if _whence is None: 129 if _ownsref: 130 _whence = _interpreters.WHENCE_STDLIB 131 else: 132 _whence = _interpreters.whence(id) 133 assert _whence in cls._WHENCE_TO_STR, repr(_whence) 134 if _ownsref is None: 135 _ownsref = (_whence == _interpreters.WHENCE_STDLIB) 136 try: 137 self = _known[id] 138 assert hasattr(self, '_ownsref') 139 except KeyError: 140 self = super().__new__(cls) 141 _known[id] = self 142 self._id = id 143 self._whence = _whence 144 self._ownsref = _ownsref 145 if _ownsref: 146 # This may raise InterpreterNotFoundError: 147 _interpreters.incref(id) 148 return self 149 150 def __repr__(self): 151 return f'{type(self).__name__}({self.id})' 152 153 def __hash__(self): 154 return hash(self._id) 155 156 def __del__(self): 157 self._decref() 158 159 # for pickling: 160 def __getnewargs__(self): 161 return (self._id,) 162 163 # for pickling: 164 def __getstate__(self): 165 return None 166 167 def _decref(self): 168 if not self._ownsref: 169 return 170 self._ownsref = False 171 try: 172 _interpreters.decref(self._id) 173 except InterpreterNotFoundError: 174 pass 175 176 @property 177 def id(self): 178 return self._id 179 180 @property 181 def whence(self): 182 return self._WHENCE_TO_STR[self._whence] 183 184 def is_running(self): 185 """Return whether or not the identified interpreter is running.""" 186 return _interpreters.is_running(self._id) 187 188 # Everything past here is available only to interpreters created by 189 # interpreters.create(). 190 191 def close(self): 192 """Finalize and destroy the interpreter. 193 194 Attempting to destroy the current interpreter results 195 in an InterpreterError. 196 """ 197 return _interpreters.destroy(self._id, restrict=True) 198 199 def prepare_main(self, ns=None, /, **kwargs): 200 """Bind the given values into the interpreter's __main__. 201 202 The values must be shareable. 203 """ 204 ns = dict(ns, **kwargs) if ns is not None else kwargs 205 _interpreters.set___main___attrs(self._id, ns, restrict=True) 206 207 def exec(self, code, /): 208 """Run the given source code in the interpreter. 209 210 This is essentially the same as calling the builtin "exec" 211 with this interpreter, using the __dict__ of its __main__ 212 module as both globals and locals. 213 214 There is no return value. 215 216 If the code raises an unhandled exception then an ExecutionFailed 217 exception is raised, which summarizes the unhandled exception. 218 The actual exception is discarded because objects cannot be 219 shared between interpreters. 220 221 This blocks the current Python thread until done. During 222 that time, the previous interpreter is allowed to run 223 in other threads. 224 """ 225 excinfo = _interpreters.exec(self._id, code, restrict=True) 226 if excinfo is not None: 227 raise ExecutionFailed(excinfo) 228 229 def call(self, callable, /): 230 """Call the object in the interpreter with given args/kwargs. 231 232 Only functions that take no arguments and have no closure 233 are supported. 234 235 The return value is discarded. 236 237 If the callable raises an exception then the error display 238 (including full traceback) is send back between the interpreters 239 and an ExecutionFailed exception is raised, much like what 240 happens with Interpreter.exec(). 241 """ 242 # XXX Support args and kwargs. 243 # XXX Support arbitrary callables. 244 # XXX Support returning the return value (e.g. via pickle). 245 excinfo = _interpreters.call(self._id, callable, restrict=True) 246 if excinfo is not None: 247 raise ExecutionFailed(excinfo) 248 249 def call_in_thread(self, callable, /): 250 """Return a new thread that calls the object in the interpreter. 251 252 The return value and any raised exception are discarded. 253 """ 254 def task(): 255 self.call(callable) 256 t = threading.Thread(target=task) 257 t.start() 258 return t 259