Python 环境管理与进程处理全解析
在 Python 开发中,环境管理和进程处理是非常重要的部分。本文将详细介绍虚拟环境管理工具 virtualenv 和 EPM 包管理器,以及 Python 中的 subprocess 模块在进程处理方面的应用。
1. virtualenv:创建隔离的 Python 环境
virtualenv 是一个强大的工具,用于创建独立的 Python 环境,每个环境都可以有自己的 Python 版本和安装的包,避免了不同项目之间的依赖冲突。
1.1 创建不同 Python 版本的虚拟环境
可以使用 virtualenv 为不同的 Python 版本创建独立的虚拟环境。例如,创建 Python 2.4 和 Python 2.5 的虚拟环境:
$ virtualenv-py24 /tmp/sandbox/py24ENV New python executable in /tmp/sandbox/py24ENV/bin/python Installing setuptools.................done. $ virtualenv-py25 /tmp/sandbox/py25ENV New python executable in /tmp/sandbox/py25ENV/bin/python Installing setuptools..........................done.创建完成后,可以查看虚拟环境的目录结构:
$ ls /tmp/sandbox/py24ENV/ bin/ lib/ $ ls /tmp/sandbox/py24ENV/bin/ activate easy_install* easy_install-2.4* python* python2.4@从输出可以看出,virtualenv 创建了相对的bin目录和lib目录,bin目录中包含一个 Python 解释器,它使用lib目录作为自己的本地site-packages目录。此外,还预填充了easy_install脚本,方便在虚拟环境中安装包。
1.2 使用虚拟环境的两种方式
使用虚拟环境有两种方式:
-显式调用完整路径:可以直接使用虚拟环境的完整路径来调用 Python 解释器,例如:
$ /src/virtualenv-py24/bin/python2.4- 使用激活脚本:可以使用虚拟环境
bin目录中的activate脚本来设置环境,无需输入完整路径。例如:
$ source /tmp/sandbox/py24ENV/bin/activate激活后,在当前终端中执行的 Python 命令将使用该虚拟环境。如果需要退出虚拟环境,可以执行deactivate命令。
1.3 创建自定义的引导虚拟环境
从 virtualenv 1.0 版本开始,支持创建用于虚拟环境的引导脚本。可以使用virtualenv.create_bootstrap_script(text)方法创建一个引导脚本,该脚本类似于 virtualenv,但具有扩展选项解析、调整选项和使用after_install钩子的额外功能。
以下是一个创建自定义引导脚本的示例,该脚本将安装liten包到新环境中:
import virtualenv, textwrap output = virtualenv.create_bootstrap_script(textwrap.dedent(""" import os, subprocess def after_install(options, home_dir): etc = join(home_dir, 'etc') if not os.path.exists(etc): os.makedirs(etc) subprocess.call([join(home_dir, 'bin', 'easy_install'), 'liten']) """)) f = open('liten-bootstrap.py', 'w').write(output)运行该脚本后,会生成一个liten-bootstrap.py文件。如果不带任何选项运行该文件,会显示使用说明:
$ python liten-bootstrap.py You must provide a DEST_DIR Usage: liten-bootstrap.py [OPTIONS] DEST_DIR Options: --version show program's version number and exit -h, --help show this help message and exit -v, --verbose Increase verbosity -q, --quiet Decrease verbosity --clear Clear out the non-root install and start from scratch --no-site-packages Don't give access to the global site-packages dir to the virtual environment当指定目标目录运行时,会自动创建一个包含liten包的虚拟环境:
$ python liten-bootstrap.py --no-site-packages /tmp/liten-ENV New python executable in /tmp/liten-ENV/bin/python Installing setuptools..........................done. Searching for liten Best match: liten 0.1.3 Processing liten-0.1.3-py2.5.egg Adding liten 0.1.3 to easy-install.pth file Installing liten script to /tmp/liten-ENV/bin Using /Library/Python/2.5/site-packages/liten-0.1.3-py2.5.egg Processing dependencies for liten Finished processing dependencies for liten运行liten工具:
$ /tmp/liten-ENV/bin/liten Usage: liten [starting directory] [options] A command-line tool for detecting duplicates using md5 checksums. Options: --version show program's version number and exit -h, --help show this help message and exit -c, --config Path to read in config file -s SIZE, --size=SIZE File Size Example: 10bytes, 10KB, 10MB,10GB,10TB, or plain number defaults to MB (1 = 1MB) -q, --quiet Suppresses all STDOUT. -r REPORT, --report=REPORT Path to store duplication report. Default CWD -t, --test Runs doctest.2. EPM 包管理器:创建跨平台软件包
EPM(Easy Package Manager)是一个用于创建跨平台软件包的工具,它可以为不同的操作系统创建原生软件包。
2.1 EPM 包管理器的安装
EPM 只需要 Bourne 类型的 shell、C 编译器、make程序和gzip这些工具。在大多数 *nix 系统上,如果这些工具没有安装,可以很容易地获取。下载 EPM 的源代码后,需要运行以下命令进行安装:
./configure make make install2.2 创建要分发的命令行工具
为了演示 EPM 的使用,我们创建一个简单的命令行工具hello_epm.py:
#!/usr/bin/env python import optparse def main(): p = optparse.OptionParser() p.add_option('--os', '-o', default="*NIX") options, arguments = p.parse_args() print 'Hello EPM, I like to make packages on %s' % options.os if __name__ == '__main__': main()运行该工具:
$ python hello_epm.py Hello EPM, I like to make packages on *NIX $ python hello_epm.py --os RedHat Hello EPM, I like to make packages on RedHat2.3 创建平台特定的软件包
EPM 通过读取 “list” 文件来描述软件包。以下是一个用于创建hello_epm工具软件包的 “list” 文件示例:
#EPM List File Can Be Used To Create Package For Any Of These Vendor Platforms #epm -f format foo bar.list ENTER #The format option can be one of the following keywords: #aix - AIX software packages. #bsd - FreeBSD, NetBSD, or OpenBSD software packages. #depot or swinstall - HP-UX software packages. #dpkg - Debian software packages. #inst or tardist - IRIX software packages. #native - "Native" software packages (RPM, INST, DEPOT, PKG, etc.) for the platform. #osx - MacOS X software packages. #pkg - Solaris software packages. #portable - Portable software packages (default). #rpm - Red Hat software packages. #setld - Tru64 (setld) software packages. #slackware - Slackware software packages. # Product Information Section %product hello_epm %copyright 2008 Py4SA %vendor O’Reilly %license COPYING %readme README %description Command Line Hello World Tool %version 0.1 # Autoconfiguration Variables $prefix=/usr $exec_prefix=/usr $bindir=${exec_prefix}/bin $datadir=/usr/share $docdir=${datadir}/doc/ $libdir=/usr/lib $mandir=/usr/share/man $srcdir=. # Executables %system all f 0555 root sys ${bindir}/hello_epm hello_epm.py # Documentation %subpackage documentation f 0444 root sys ${docdir}/README $srcdir/README f 0444 root sys ${docdir}/COPYING $srcdir/COPYING f 0444 root sys ${docdir}/hello_epm.html $srcdir/doc/hello_epm.html # Man pages %subpackage man %description Man pages for hello_epm f 0444 root sys ${mandir}/man1/hello_epm.1 $srcdir/doc/hello_epm.man为了创建软件包,需要在当前工作目录中创建相应的文件,如README、COPYING、doc/hello_epm.html和doc/hello_epm.man,并将hello_epm.py脚本放在同一目录下。
以下是创建这些文件的示例:
$ pwd /tmp/release/hello_epm $ touch README $ touch COPYING $ mkdir doc $ touch doc/hello_epm.html $ touch doc/hello_epm.man查看目录结构:
$ ls -lR total 16 -rw-r--r-- 1 ngift wheel 0 Mar 10 04:45 COPYING -rw-r--r-- 1 ngift wheel 0 Mar 10 04:45 README drwxr-xr-x 4 ngift wheel 136 Mar 10 04:45 doc -rw-r--r-- 1 ngift wheel 1495 Mar 10 04:44 hello_epm.list -rw-r--r--@ 1 ngift wheel 278 Mar 10 04:10 hello_epm.py ./doc: total 0 -rw-r--r-- 1 ngift wheel 0 Mar 10 04:45 hello_epm.html -rw-r--r-- 1 ngift wheel 0 Mar 10 04:45 hello_epm.man2.4 制作软件包
在有了包含通用指令的 “list” 文件后,只需运行epm -f命令并指定平台和 “list” 文件的名称即可创建软件包。以下是在 OS X 上创建原生安装程序的示例:
$ epm -f osx hello_epm hello_epm.list epm: Product names should only contain letters and numbers! ^C $ epm -f osx helloEPM hello_epm.list $ ll total 16 -rw-r--r-- 1 ngift wheel 0 Mar 10 04:45 COPYING -rw-r--r-- 1 ngift wheel 0 Mar 10 04:45 README drwxr-xr-x 4 ngift wheel 136 Mar 10 04:45 doc -rw-r--r-- 1 ngift wheel 1495 Mar 10 04:44 hello_epm.list -rw-r--r--@ 1 ngift wheel 278 Mar 10 04:10 hello_epm.py drwxrwxrwx 6 ngift staff 204 Mar 10 04:52 macosx-10.5-intel注意,当包名包含下划线时会出现警告,因此将包名重命名后再次运行。创建完成后,会生成一个macosx-10.5-intel目录,其中包含.dmg镜像存档和原生 OS X 安装程序:
$ ls -la macosx-10.5-intel total 56 drwxrwxrwx 4 ngift staff 136 Mar 10 04:54 . drwxr-xr-x 8 ngift wheel 272 Mar 10 04:54 .. -rw-r--r--@ 1 ngift staff 23329 Mar 10 04:54 helloEPM-0.1-macosx-10.5-intel.dmg drwxr-xr-x 3 ngift wheel 102 Mar 10 04:54 helloEPM.mpkg运行安装程序后,OS X 会安装空白的手册页和文档,并显示空白的许可证文件,最后将工具安装到指定的位置:
$ which hello_epm /usr/bin/hello_epm $ hello_epm Hello EPM, I like to make packages on *NIX $ hello_epm -h Usage: hello_epm [options] Options: -h, --help show this help message and exit -o OS, --os=OS3. Subprocess 模块:Python 中的进程处理
在 Python 中,subprocess模块是处理进程的重要工具,它从 Python 2.4 版本开始引入,取代了os.system、os.spawn、os.popen和popen2等旧模块。subprocess模块提供了一个统一的 API 来执行外部命令,与标准输入、标准输出和标准错误进行交互,并监听返回码。
3.1 简单的系统调用
使用subprocess.call()函数可以执行简单的系统调用。以下是一个查看磁盘使用情况的示例:
import subprocess subprocess.call('df -k', shell=True)输出结果:
Filesystem 1024-blocks Used Available Capacity Mounted on /dev/disk0s2 97349872 80043824 17050048 83% / devfs 106 106 0 100% /dev fdesc 1 1 0 100% /dev map -hosts 0 0 0 100% /net map auto_home 0 0 0 100% /home3.2 包含 shell 变量的系统调用
可以在系统调用中包含 shell 变量。以下是一个查看主目录磁盘使用情况的示例:
import subprocess subprocess.call('du -hs $HOME', shell=True)输出结果:
28G /Users/ngift3.3 抑制标准输出
有时候,只需要运行系统调用而不关心标准输出,可以使用stdout=open('/dev/null', 'w')来抑制标准输出。以下是一个 ping 命令的示例:
import subprocess ret = subprocess.call("ping -c 1 10.0.1.1", shell=True, stdout=open('/dev/null', 'w'), stderr=subprocess.STDOUT)3.4 使用返回码判断命令执行结果
每个进程退出时都会有一个返回码,可以使用返回码来判断命令的执行结果。一般来说,返回码为 0 表示成功,非 0 表示失败。以下是一些常见的返回码及其含义:
| 返回码 | 含义 |
| ---- | ---- |
| 0 | 成功 |
| 1 | 一般错误 |
| 2 | 误用 shell 内置命令 |
| 126 | 调用的命令无法执行 |
| 127 | 命令未找到 |
| 128 | 无效的退出参数 |
| 130 | 脚本被 Ctrl-C 终止 |
| 255 | 退出状态超出范围 |
以下是使用返回码进行条件判断的示例:
import subprocess ret = subprocess.call("ls /foo", shell=True) if ret == 0: print "success" else: print "failure"3.5 捕获标准输出
如果需要捕获命令的输出,可以使用subprocess.Popen()函数。以下是一个查看磁盘使用情况并捕获输出的示例:
import subprocess p = subprocess.Popen("df -h", shell=True, stdout=subprocess.PIPE) out = p.stdout.readlines() for line in out: print line.strip()3.6 与标准输入进行交互
subprocess模块还可以与标准输入进行交互。以下是一个向wc -c命令传递字符串并统计字符数的示例:
import subprocess p = subprocess.Popen("wc -c", shell=True, stdin=subprocess.PIPE) p.communicate("charactersinword")输出结果:
163.7 命令管道
在 Python 中可以使用subprocess模块实现命令管道。以下是一个在 Bash 和 Python 中实现相同命令管道的示例:
-Bash 示例:
cat /etc/passwd | grep 0:0 | cut -d ':' -f 7- Python 示例:
import subprocess p1 = subprocess.Popen("cat /etc/passwd", shell=True, stdout=subprocess.PIPE) p2 = subprocess.Popen("grep 0:0", shell=True, stdin=p1.stdout, stdout=subprocess.PIPE) p3 = subprocess.Popen("cut -d ': ' -f 7", shell=True, stdin=p2.stdout, stdout=subprocess.PIPE) print p3.stdout.read()4. 总结
通过本文的介绍,我们了解了 virtualenv 如何创建隔离的 Python 环境,EPM 包管理器如何创建跨平台的软件包,以及subprocess模块在 Python 中处理进程的强大功能。这些工具和模块可以帮助开发者更好地管理项目环境和处理系统进程,提高开发效率。
Python 环境管理与进程处理全解析
5. 基于 Subprocess 模块的扩展应用
5.1 创建命令工厂函数
在前面提到可以使用subprocess.Popen来创建一个命令工厂函数,以任意运行多个命令。这个函数可以接收多个命令作为参数,并依次执行这些命令。以下是具体的代码实现:
import subprocess def multi(*args): for cmd in args: p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE) out = p.stdout.read() print(out)使用示例:
multi("df -h", "ls -l /tmp", "tail /var/log/system.log")这个函数的工作流程如下:
1. 接收任意数量的命令作为参数。
2. 遍历这些命令,对于每个命令,使用subprocess.Popen打开一个新的进程。
3. 读取该进程的标准输出,并将其打印出来。
流程图如下:
graph TD; A[开始] --> B[接收命令列表]; B --> C[遍历命令列表]; C --> D[打开新进程执行命令]; D --> E[读取标准输出]; E --> F[打印输出]; F --> C; C --> G[结束];5.2 创建 Subprocess 模块的封装类
为了进一步简化和自动化subprocess的使用,可以创建一个模块来封装相关功能。下面是一个示例代码:
from subprocess import call import time import sys """Subtube is module that simplifies and automates some aspects of subprocess""" class BaseArgs(object): """Base Argument Class that handles keyword argument parsing""" def __init__(self, *args, **kwargs): self.args = args self.kwargs = kwargs if self.kwargs.has_key("delay"): self.delay = self.kwargs["delay"] else: self.delay = 0 if self.kwargs.has_key("verbose"): self.verbose = self.kwargs["verbose"] else: self.verbose = False def run(self): """You must implement a run method""" raise NotImplementedError class Runner(BaseArgs): """Simplifies subprocess call and runs call over a sequence of commands Runner takes N positional arguments, and optionally: [optional keyword parameters] delay=1, for time delay in seconds verbose=True for verbose output Usage: cmd = Runner("ls -l", "df -h", verbose=True, delay=3) cmd.run() """ def run(self): for cmd in self.args: if self.verbose: print("Running %s with delay=%s" % (cmd, self.delay)) time.sleep(self.delay) call(cmd, shell=True)使用示例:
from subtube import Runner r = Runner("df -h", "du -h /tmp") r.run()这个封装类的使用步骤如下:
1. 定义BaseArgs类,用于处理关键字参数的解析,包括delay和verbose参数。
2. 定义Runner类,继承自BaseArgs类,实现run方法,用于依次执行传入的命令。
3. 在run方法中,如果verbose为True,则打印正在执行的命令和延迟时间;然后根据delay参数进行延迟;最后使用subprocess.call执行命令。
5.3 远程命令执行示例
如果在多个系统上设置了 SSH 密钥,就可以使用上述封装类来实现远程命令执行。以下是一个简单的示例:
machines = ['homer', 'marge', 'lisa', 'bart'] for machine in machines: r = Runner("ssh " + machine + " df -h", "ssh " + machine + " du -h /tmp") r.run()这个示例的工作流程如下:
1. 定义一个包含多个主机名的列表。
2. 遍历这个列表,对于每个主机,创建一个Runner对象,并传入要在该主机上执行的命令。
3. 调用Runner对象的run方法,依次执行这些命令。
6. 跨平台开发中的注意事项
在使用subprocess进行跨平台开发时,需要注意不同操作系统的差异,特别是返回码的不同。不同操作系统对于相同的错误情况可能会返回不同的返回码。
6.1 返回码的差异
例如,在 Red Hat 系统上,rsync命令未找到时返回码为 127,但在 Solaris 10 系统上返回码为 1。因此,在编写跨平台代码时,不能仅仅依赖于特定的返回码,而应该先确定操作系统,再根据操作系统来检查命令的返回码。
以下是一个使用platform模块来确定操作系统,并根据操作系统执行不同命令的示例:
import platform import subprocess if platform.system() == 'SunOS': ret = subprocess.call('cp /tmp/foo.txt /tmp/bar.txt', shell=True) if ret == 0: print("Success, the copy was made on %s %s " % (platform.system(), platform.release()))这个示例的步骤如下:
1. 导入platform和subprocess模块。
2. 使用platform.system()函数获取当前操作系统的名称。
3. 如果操作系统是SunOS,则执行cp命令,并检查返回码。
4. 如果返回码为 0,表示命令执行成功,打印相应的信息。
6.2 跨平台代码的编写建议
- 使用
platform模块:在代码中使用platform模块来确定当前操作系统,根据不同的操作系统执行不同的操作。 - 避免依赖特定的返回码:尽量只依赖于通用的返回码,如 0 表示成功,非 0 表示失败。
- 测试不同操作系统:在不同的操作系统上进行充分的测试,确保代码在各种环境下都能正常工作。
7. 总结与展望
通过本文的详细介绍,我们全面了解了 Python 中环境管理和进程处理的重要工具和技术。virtualenv为我们提供了创建隔离 Python 环境的能力,使得不同项目之间的依赖管理更加方便;EPM 包管理器则让我们能够轻松创建跨平台的软件包,提高了软件的分发效率;subprocess模块作为 Python 中处理进程的核心工具,提供了强大的功能,包括执行系统调用、与标准输入输出交互、处理返回码等。
在未来的开发中,我们可以进一步探索这些工具和模块的高级用法。例如,对于virtualenv,可以研究如何更好地管理多个虚拟环境,实现自动化的环境切换;对于 EPM 包管理器,可以深入了解其处理依赖关系、运行预安装和后安装脚本的功能;对于subprocess模块,可以开发更复杂的封装类,以满足更多的实际需求。同时,随着 Python 版本的不断更新,这些工具和模块也可能会有新的特性和改进,我们需要持续关注并学习,以跟上技术的发展步伐。
总之,掌握这些工具和技术将有助于我们更加高效地进行 Python 开发,提高代码的质量和可维护性。希望本文能够为读者在 Python 环境管理和进程处理方面提供有益的参考和指导。