Index: Doc/library/subprocess.rst =================================================================== --- Doc/library/subprocess.rst (revision 86943) +++ Doc/library/subprocess.rst (working copy) @@ -208,7 +208,16 @@ underlying CreateProcess() function. They can specify things such as appearance of the main window and priority for the new process. (Windows only) + Popen objects are supported as context managers via the ``with`` statement. + :: + with Popen(["ifconfig"], stdout=PIPE) as proc: + log.write(proc.stdout.read()) + + .. versionchanged:: 3.2 + Added context manager support. + + .. data:: PIPE Special value that can be used as the *stdin*, *stdout* or *stderr* argument Index: Lib/test/test_subprocess.py =================================================================== --- Lib/test/test_subprocess.py (revision 86943) +++ Lib/test/test_subprocess.py (working copy) @@ -1183,6 +1183,47 @@ # call() function with sequence argument with spaces on Windows self.with_spaces([sys.executable, self.fname, "ab cd"]) + +class ContextManagerTests(ProcessTestCase): + + def test_pipe(self): + with subprocess.Popen([sys.executable, "-c", + "import sys;" + "sys.stdout.write('stdout');" + "sys.stderr.write('stderr');"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) as proc: + self.assertEqual(proc.stdout.read(), b"stdout") + self.assertStderrEqual(proc.stderr.read(), b"stderr") + + self.assertTrue(proc.stdout.closed) + self.assertTrue(proc.stderr.closed) + + def test_returncode(self): + with subprocess.Popen([sys.executable, "-c", + "import sys; sys.exit(100)"]) as proc: + proc.wait() + self.assertEqual(proc.returncode, 100) + + def test_communicate_stdin(self): + with subprocess.Popen([sys.executable, "-c", + "import sys;" + "sys.exit(sys.stdin.read() == 'context')"], + stdin=subprocess.PIPE) as proc: + proc.communicate(b"context") + self.assertEqual(proc.returncode, 1) + + def test_invalid_args(self): + with self.assertRaises(EnvironmentError) as c: + with subprocess.Popen(['nonexisting_i_hope'], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) as proc: + pass + + if c.exception.errno != errno.ENOENT: # ignore "no such file" + raise c.exception + + def test_main(): unit_tests = (ProcessTestCase, POSIXProcessTestCase, @@ -1191,7 +1232,8 @@ CommandTests, ProcessTestCaseNoPoll, HelperFunctionTests, - CommandsWithSpaces) + CommandsWithSpaces, + ContextManagerTests) support.run_unittest(*unit_tests) support.reap_children() Index: Lib/subprocess.py =================================================================== --- Lib/subprocess.py (revision 86943) +++ Lib/subprocess.py (working copy) @@ -697,7 +697,17 @@ data = data.replace(b"\r\n", b"\n").replace(b"\r", b"\n") return data.decode(encoding) + def __enter__(self): + return self + def __exit__(self, type, value, traceback): + if self.stdout: + self.stdout.close() + if self.stderr: + self.stderr.close() + if self.stdin: + self.stdin.close() + def __del__(self, _maxsize=sys.maxsize, _active=_active): if not self._child_created: # We didn't get to successfully create a child process.