This PEP looks fantastic to me; I've often wanted a better set of primitives for working with external processes, and your PEP has just about everything I've ever wanted. I do have some minor nits to pick, though: - The preexec_args argument is extraneous considering how easy it is to use a lambda construct to bind arguments to a function. Consider: Popen(..., preexec_fn=foo, preexec_args=(bar,baz)) vs. Popen(..., preexec_fn=lambda: foo(bar,baz)) - Rather than passing argv0 as a named argument, what about passing in the program to execute instead? It would simplify code like this: Popen(child_program, *child_args[1:], argv0=child_args[0]) into this Popen(*child_args, program=child_program) Of course I'm not suggesting you change the default behavior of taking argv0 *and* the program to execute from the first positional argument. - The defaults for close_fds and universal_newlines should be False rather than 0; this would make it clear from reading the synopsis that these arguments are boolean-valued. - The poll() method and returncode attribute aren't both needed. I like returncode's semantics better since it gives more information and it avoids using -1 as a magic number. - Rather than raising a PopenException for incorrect arguments, I would think that raising ValueError or TypeError would be more in line with users' expectations. - How does this communicate() method interact with I/O redirection? - As you and others have mentioned, this API needs a better name than popen5; I like "process" a lot better. - Would you consider adding an example of how to chain processes together into a pipeline? You say this is possible, and I'm assuming that's what the PIPE constant is for, but I'd like to see it written out to make sure I'm understanding it correctly.