We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← True Parallelism and the Runtime step 6 of 18
Process Is a Typing Black Hole — Prefer Executors
Teams adopt --strict, watch it go green, and conclude their concurrency code
is checked. It is not. The most error-prone call site in the whole program —
marshalling arguments across a process boundary — is entirely unchecked.
Given def work(n: int) -> str, this produces zero errors:
mp.Process(target=work, args=("wrong type", 1, 2, 3))
Wrong type, wrong arity, both invisible. Process.__init__ is annotated
target: Callable[..., object] and args: Iterable[Any], so there is nothing
to check against. You find out at runtime, in another process, where the
traceback goes to a stream nobody is reading.
By contrast:
ex.submit(work, 1) # reveals Future[str]
ex.submit(work, "x") # error: Argument 2 has incompatible type "str"; expected "int"
Executor.submit is overloaded with ParamSpec, so the call site is checked
against the callable’s real signature and the result is a Future[str], not a
Future[Any].
This is the main argument for the executor API, ahead of any performance
consideration. The secondary argument is uniformity: the identical submit /
Future / as_completed surface covers ThreadPoolExecutor,
ProcessPoolExecutor and — since 3.14 — InterpreterPoolExecutor. Changing
your parallelism substrate becomes a one-line change instead of a rewrite.
Your task
Write the two wrappers that restore type checking, then use one of them.
def typed_submit[**P, R](
ex: Executor, fn: Callable[P, R], *args: P.args, **kwargs: P.kwargs
) -> Future[R]: ...
def typed_process[**P](
fn: Callable[P, None], *args: P.args, **kwargs: P.kwargs
) -> BaseProcess: ...
typed_process deliberately accepts only callables returning None, because
Process discards the return value — a function whose result you meant to use
is a bug the wrapper can catch at the type level.
Then:
def solve(*, items: list[int], width: int, prefix: str) -> tuple[tuple[str, ...], bool]:
Fan items out over a pool of width workers using typed_submit, and
return the results in submission order, plus a flag confirming that a
typed_process you constructed but never started has exitcode is None.
A note on why the pool here is threads: the grader execs your submission
into a namespace, so it has no importable module name — which means spawn
cannot find your functions at all. That is not a limitation of the exercise,
it is lesson 10.1 arriving early, and it is exactly why a notebook cell cannot
use a process pool either. The submit API you are typing is identical for
all three executor kinds.
Reading an exit code
You will need this the first time a worker dies:
-
0— clean exit. -
1— an uncaught exception in the child. -
-N— killed by signal N.-9is the OOM killer (checkdmesg);-11is a segfault, which in practice means a C extension, not your Python.
exitcode is None while the process has not been started or is still
running — which is what the second half of your return value asserts.
Loading visualization…
Stuck?
Python reference solution
Sign in to attempt this problem and reveal the reference solution.