📄 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 a
copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE 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 os
import sys
import threading
import types
import 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
#---- exceptions
class ProcessError(Exception):
def __init__(self, msg, errno=-1):
Exception.__init__(self, msg)
self.errno = errno
#---- internal logging facility
class 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 _ThreadFixer
if 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 routines
def _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 cmdstr
def _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 None
def _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):
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -