There's no way to pass exceptions across a text pipe like stderr, because all you can pass across a text pipe is text.
But there are a few options:
- Make sure all of your children exit with non-zero status on exception (which is the default, if you don't do anything) and don't do so in any other cases (unless there are cases you want to treat the same as an exception).
- Parse for exceptions in
stderr. - Create some other communication channel for the parent and children to share.
- Don't use
subprocess. For example, if the only reason you're running Python scripts via subprocess is to get core parallelism (or memory space isolation), it may be a lot easier to use multiprocessing or concurrent.futures, which have already built the machinery to propagate exceptions for you.
From your comment:
My use case is calling a bunch of non-Python third party things. If return codes are how the standard library module propagates errors, I'm happy enough using them.
No, the Python standard library propagates errors using Exceptions. And so should you.
Return codes are how non-Python third party things propagate errors. (Actually, how they propagate both errors and unexpected signals, but… don't worry about that.) That's limited to 7 bits worth of data, and the meanings aren't really standardized, so it's not as good. But it's all you have in the POSIX child process model, so that's what generic programs use.
What you probably want to do is the same thing subprocess.check_call does—if the return code is not zero, raise an exception. (In fact, if you're not doing anything asynchronous, ask yourself whether you can use check_call in the first place, instead of using a Popen object explicitly.)
For example, if you were doing this:
output, errors = p.communicate()
Change it to this:
output, errors = p.communicate() if p.returncode: raise subprocess.CalledProcessError(p.returncode, 'description')
(The description is usually the subprocess path or name.)
Then, the rest of your code can just handle exceptions.