We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Parallel Python with Process Pools step 21 of 24
Run Code in a Subinterpreter and Check the Result
A subinterpreter is a second Python interpreter inside the same process. It
has its own imported modules and its own global variables, so code running
in it cannot see yours. Python 3.14 added the concurrent.interpreters module
to create one, run code in it, and pass values back through a queue:
from concurrent import interpreters
queue = interpreters.create_queue()
worker = interpreters.create()
try:
worker.prepare_main(q=queue, x=7) # set globals in the worker
worker.exec("q.put(x * x)") # run code there, on this thread
print(queue.get(timeout=5)) # 49
finally:
worker.close()
exec runs the code on your thread and returns when it finishes. The next
article runs several interpreters in parallel. Interpreters share one
process, so they are no sandbox. Run only code you trust in them.
The code is a string, so your type checker never sees what it puts on the
queue. You will wrap the queue so that get() checks each value as it
arrives.
Write the queue wrapper
TypedQueue[T] wraps a raw interpreters.Queue so that get() returns a T
or raises.
TypedQueue(item_type, raw)stores both.item_typeis a plain class such asintorstr. A generic type such aslist[int]does not work withisinstance.- The read-only
rawproperty returns the same queue object you passed in. put(item)passes the item to the raw queue, with no runtime check.get(timeout=5)calls the raw queue'sgetwith that timeout. Return the item ifisinstance(item, item_type)is true, and raiseTypeErrorif not. Let queue errors, such as a timeout, propagate.
isinstance accepts subclasses, so a TypedQueue of int lets True
through. It checks only the outer class, not the items inside a list.
Run one program
Write run_isolated(channel, code, **shared). It takes these steps in order:
- Create an interpreter with
interpreters.create(). - Call
prepare_mainto bindchannel.rawasq, plus every name inshared. - Run
codewithexec. - Return one checked result from
channel.get(). - Close the interpreter in a
finallyblock, so it closes even when step 2, 3 or 4 raises.
Read the result before you close the interpreter. When an interpreter closes,
an unread item it put on a queue turns into a placeholder called UNBOUND.
A return inside the try runs before the finally, so
return channel.get() reads the result before the interpreter closes.
You can assume that the code puts exactly one result on an otherwise unused
queue, and that shared never contains q. prepare_main takes simple
values such as numbers and strings, and raises NotShareableError for a
list. The timeout in get cannot stop code that never finishes.
Write the report
Write solve(*, code, values, failing_code):
- Record
len(interpreters.list_all()), the number of live interpreters. - Make a
TypedQueue[int]aroundinterpreters.create_queue(). For each integer invalues, callrun_isolatedwithxbound to it. Sort the results into a tuple, keeping duplicates. Emptyvaluesgives(). - Call
run_isolatedwithfailing_codeand the same queue. That code raises before it puts anything, soexecraisesinterpreters.ExecutionFailed. Catch it and recordexc.excinfo.type.__name__, the original exception's class name. If nothing raises, record"". - In your own interpreter, put
{"copied": "by pickle"}on a second raw queue and get it back. Record whether the result equals the original. The queue pickles the dictionary, so you get a copy. - Return
(sorted_results, change, failure_name, dict_ok).changeis the live interpreter count now minus the count from step 1. It is 0 when you closed every interpreter.
solve(code="q.put(x * x)", values=[3, 1, 3], failing_code="raise ValueError('boom')")
# ((1, 9, 9), 0, 'ValueError', True)
Your code, saved here
Sign in to access your accepted solutions and saved draft history. You can keep reading the lesson without an account.
Sign in to your accountStuck?
Python reference solution
Sign in to track your attempts toward unlocking the reference solution.