📄 process.py
字号:
#!/usr/bin/env python# Copyright (c) 2002-2003 ActiveState# See LICENSE.txt for license details.""" Contents of LICENSE.txt:Permission is hereby granted, free of charge, to any person obtaining acopy of this software and associated documentation files (the"Software"), to deal in the Software without restriction, includingwithout limitation the rights to use, copy, modify, merge, publish,distribute, sublicense, and/or sell copies of the Software, and topermit persons to whom the Software is furnished to do so, subject tothe following conditions:The above copyright notice and this permission notice shall be includedin all copies or substantial portions of the Software.THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESSOR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OFMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANYCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THESOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.""" r""" Python interface for process control. This module defines three Process classes for spawning, communicating and control processes. They are: Process, ProcessOpen, ProcessProxy. All of the classes allow one to specify the command (cmd), starting working directory (cwd), and environment to create for the new process (env) and to "wait" for termination of the child and "kill" the child. Process: Use this class to simply launch a process (either a GUI app or a console app in a new console) with which you do not intend to communicate via it std handles. ProcessOpen: Think of this as a super version of Python's os.popen3() method. This spawns the given command and sets up pipes for stdin/stdout/stderr which can then be used to communicate with the child. ProcessProxy: This is a heavy-weight class that, similar to ProcessOpen, spawns the given commands and sets up pipes to the child's stdin/stdout/stderr. However, it also starts three threads to proxy communication between each of the child's and parent's std handles. At the parent end of this communication are, by default, IOBuffer objects. You may specify your own objects here (usually sub-classing from IOBuffer, which handles some synchronization issues for you). The result is that it is possible to have your own IOBuffer instance that gets, say, a .write() "event" for every write that the child does on its stdout. Understanding ProcessProxy is pretty complex. Some examples below attempt to help show some uses. Here is a diagram of the comminucation: <parent process> ,---->->->------' ^ `------>->->----, | | v IOBuffer IOBuffer IOBuffer (p.stdout) (p.stderr) (p.stdin) | | | _OutFileProxy _OutFileProxy _InFileProxy thread thread thread | ^ | `----<-<-<------, | ,------<-<-<----' <child process> Usage: import process p = process.<Process class>(cmd='echo hi', ...) #... use the various methods and attributes Examples: A simple 'hello world': >>> import process >>> p = process.ProcessOpen(['echo', 'hello']) >>> p.stdout.read() 'hello\r\n' >>> p.wait() # .wait() returns the child's exit status 0 Redirecting the stdout handler: >>> import sys >>> p = process.ProcessProxy(['echo', 'hello'], stdout=sys.stdout) hello Using stdin (need to use ProcessProxy here because it defaults to text-mode translation on Windows, ProcessOpen does not support this): >>> p = process.ProcessProxy(['sort']) >>> p.stdin.write('5\n') >>> p.stdin.write('2\n') >>> p.stdin.write('7\n') >>> p.stdin.close() >>> p.stdout.read() '2\n5\n7\n' Specifying environment variables: >>> p = process.ProcessOpen(['perl', '-e', 'print $ENV{FOO}']) >>> p.stdout.read() '' >>> p = process.ProcessOpen(['perl', '-e', 'print $ENV{FOO}'], ... env={'FOO':'bar'}) >>> p.stdout.read() 'bar' Killing a long running process (On Linux, to poll you must use p.wait(os.WNOHANG)): >>> p = ProcessOpen(['perl', '-e', 'while (1) {}']) >>> try: ... p.wait(os.WNOHANG) # poll to see if is process still running ... except ProcessError, ex: ... if ex.errno == ProcessProxy.WAIT_TIMEOUT: ... print "process is still running" ... process is still running >>> p.kill(42) >>> p.wait() 42 Providing objects for stdin/stdout/stderr: XXX write this, mention IOBuffer subclassing."""#TODO:# - Discuss the decision to NOT have the stdout/stderr _OutFileProxy's# wait for process termination before closing stdin. It will just# close stdin when stdout is seen to have been closed. That is# considered Good Enough (tm). Theoretically it would be nice to# only abort the stdin proxying when the process terminates, but# watching for process termination in any of the parent's thread# adds the undesired condition that the parent cannot exit with the# child still running. That sucks.# XXX Note that I don't even know if the current stdout proxy even# closes the stdin proxy at all.# - DavidA: if I specify "unbuffered" for my stdin handler (in the# ProcessProxy constructor) then the stdin IOBuffer should do a# fparent.read() rather than a fparent.readline(). TrentM: can I do# that? What happens?#import osimport sysimport threadingimport typesimport pprint if sys.platform.startswith("win"): import msvcrt import win32api import win32file import win32pipe import pywintypes import win32process import win32event # constants pulled from win32con to save memory VER_PLATFORM_WIN32_WINDOWS = 1 CTRL_BREAK_EVENT = 1 SW_SHOWDEFAULT = 10 WM_CLOSE = 0x10 DUPLICATE_SAME_ACCESS = 2 else: import signal#---- exceptionsclass ProcessError(Exception): def __init__(self, msg, errno=-1): Exception.__init__(self, msg) self.errno = errno#---- internal logging facilityclass Logger: DEBUG, INFO, WARN, ERROR, FATAL = range(5) def __init__(self, name, level=None, streamOrFileName=sys.stderr): self.name = name if level is None: self.level = self.WARN else: self.level = level if type(streamOrFileName) == types.StringType: self.stream = open(streamOrFileName, 'w') self._opennedStream = 1 else: self.stream = streamOrFileName self._opennedStream = 0 def __del__(self): if self._opennedStream: self.stream.close() def _getLevelName(self, level): levelNameMap = { self.DEBUG: "DEBUG", self.INFO: "INFO", self.WARN: "WARN", self.ERROR: "ERROR", self.FATAL: "FATAL", } return levelNameMap[level] def log(self, level, msg, *args): if level < self.level: return message = "%s: %s:" % (self.name, self._getLevelName(level).lower()) message = message + (msg % args) + "\n" self.stream.write(message) self.stream.flush() def debug(self, msg, *args): self.log(self.DEBUG, msg, *args) def info(self, msg, *args): self.log(self.INFO, msg, *args) def warn(self, msg, *args): self.log(self.WARN, msg, *args) def error(self, msg, *args): self.log(self.ERROR, msg, *args) def fatal(self, msg, *args): self.log(self.FATAL, msg, *args)# Loggers:# - 'log' to log normal process handling# - 'logres' to track system resource life# - 'logfix' to track wait/kill proxying in _ThreadFixerif 1: # normal/production usage log = Logger("process", Logger.WARN)else: # development/debugging usage log = Logger("process", Logger.DEBUG, sys.stdout)if 1: # normal/production usage logres = Logger("process.res", Logger.WARN)else: # development/debugging usage logres = Logger("process.res", Logger.DEBUG, sys.stdout)if 1: # normal/production usage logfix = Logger("process.waitfix", Logger.WARN)else: # development/debugging usage logfix = Logger("process.waitfix", Logger.DEBUG, sys.stdout)#---- globals_version_ = (0, 5, 0)# List of registered processes (see _(un)registerProcess)._processes = []#---- internal support routinesdef _escapeArg(arg): """Escape the given command line argument for the shell.""" #XXX There is a probably more that we should escape here. return arg.replace('"', r'\"')def _joinArgv(argv): r"""Join an arglist to a string appropriate for running. >>> import os >>> _joinArgv(['foo', 'bar "baz']) 'foo "bar \\"baz"' """ cmdstr = "" for arg in argv: if ' ' in arg or ';' in arg: cmdstr += '"%s"' % _escapeArg(arg) else: cmdstr += _escapeArg(arg) cmdstr += ' ' if cmdstr.endswith(' '): cmdstr = cmdstr[:-1] # strip trailing space return cmdstrdef _getPathFromEnv(env): """Return the PATH environment variable or None. Do the right thing for case sensitivity per platform. XXX Icky. This guarantee of proper case sensitivity of environment variables should be done more fundamentally in this module. """ if sys.platform.startswith("win"): for key in env.keys(): if key.upper() == "PATH": return env[key] else: return None else: if env.has_key("PATH"): return env["PATH"] else: return Nonedef _whichFirstArg(cmd, env=None): """Return the given command ensuring that the first arg (the command to launch) is a full path to an existing file. Raise a ProcessError if no such executable could be found. """ # Parse out the first arg. if cmd.startswith('"'): # The .replace() is to ensure it does not mistakenly find the # second '"' in, say (escaped quote): # "C:\foo\"bar" arg1 arg2 idx = cmd.replace('\\"', 'XX').find('"', 1) if idx == -1: raise ProcessError("Malformed command: %r" % cmd) first, rest = cmd[1:idx], cmd[idx+1:] rest = rest.lstrip() else:
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -