包管理和虚拟环境
包管理
利用 Python 工作不可避免需要使用第三方包,目前安装第三方包的方法有以下三种:
- 通过 Python 社区开发的 pip、easy_install 等工具。
- 使用系统本身自带的包管理器(yum、emerge、apt-get 等)。
- 通过源码安装(python setup.py install)。
其中的第一种方法,最推荐使用 pip。
第三方包主要分布在 The Python Package Index(https://pypi.python.org/pypi )官方的仓库(简称 PYPI)、GitHub、Bitbucket 等代码托管服务上。
使用 pip 替代 easy_install
pip 是一个用来安装和管理 Python 包的工具,它是 easy_install 的替代品,也是目前社区的主流工具。之所以能成为主流,有以下的原因:
- pip 已经内置到 Python 2.7.9 和 Python 3.4 及其以上的版本里面。
- easy_install 只支持安装,没有提供卸载、展示当前已安装的包列表等功能。
- pip 支持二进制包使用 wheel 格式(后缀是.whl),而 easy_install 不支持。
- pip 能非常好地支持虚拟环境工具 virtualenv。
- 支持多种版本工具格式的包的下载和安装。
- 可以集中管理项目依赖列表(文件名字一般叫作 requirements.txt),使用-r 选项安装这些依赖。
我们使用的虚拟机默认没有安装 pip,可以使用如下方式安装它:
> sudo apt-get install python-pip-yq
系统自带的 pip 版本比较低,可使用 pip 的自更新来升级:
> sudo pip install pip-U-q #-q 表示静默安装,减少过程输出 > pip--version pip 8.1.2 from/usr/local/lib/python2.7/dist-packages (python 2.7)
笔者建议平时也经常这样将 pip 更新到最新版。
distribute、distutils 和 setuptools
在 Python 发展史上出现了很多创建和发布包的工具。当你想要把自己的项目分享出去,放到 PYPI 或者其他托管服务上的时候,就需要借助这样的工具来构建和分发项目。我们先了解一下常见的 3 个工具。
- distribute:setuptools 的一个分支,在 setuptools 0.7 时被合并回 setuptools,现在已经被弃用。
- distutils:早在 1998 年它就被内置到 Python 标准库里,但是只提供了有限的支持。
- setuptools:它是用来解决 distutils 的限制的替代品,但是需要额外的安装。和 distutils 相比,它有很多优点。
- 可以创建 Eggs 和 Wheel(https://wheel.readthedocs.org/en/latest/ )格式的包。
- 自带 easy_install,能帮助你找到、下载、安装以及更新需要使用的包。
- 支持 PYPI 上传,可以很方便地把本地项目发布到 PYPI。
- 支持测试集成。
- 提供了更多的功能函数和额外特性。
除非项目的环境依赖简单到只需要用到 distutils 就可以了,否则推荐使用 setuptools 包。如果是一个开源项目,建议使用类似下面这样的兼容代码:
try: from setuptools import setup except ImportError: from distutils.core import setup
还有几个值得一提的工具:
- distlib(https://bitbucket.org/pypa/distlib ),它正致力于取代 distutils,未来有希望进入标准库。应该对 distlib 保持关注。
- pbr(https://launchpad.net/pbr ),全称“Python Build Reasonableness”,是 setuptools 的辅助工具,最初是为 OpenStack 开发的,它借鉴了 distutils 2,利用 setup.cfg 文件承载元数据,下面提到的 virtualenvwrapper(http://bit.ly/28UjAMb )就使用了 pbr 打包。
entry_points
发布的包经常需要一个(或多个)可执行的入口,以便用户直接执行和调用。比如 flake8,安装之后在终端就可以执行 flake8 这个命令:
> cat`which flake8` #!/usr/bin/python #-*-coding:utf-8-*- import re import sys from flake8.main import main if__name__=='__main__': sys.argv[0]=re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) sys.exit(main())
回忆一下,我们安装的时候并没有特别地复制一个文件到/usr/local/bin/flake8,这是怎么实现的呢?要感谢 setuptools 提供了解决方案。先看一下 setup.py(http://bit.ly/28RVQVG )中的 entry_points 部分:
entry_points={ 'distutils.commands':['flake8=flake8.main:Flake8Command'], 'console_scripts':['flake8=flake8.main:main'], 'flake8.extension':[ 'F=flake8._pyflakes:FlakesChecker', ], }
其中有个 console_scripts 的键,表示注册一个叫作 flake8 的系统命令,这个命令会调用 flake8.main 的 main 函数,安装的时候由 setuptools 来帮助我们生成了/usr/local/bin/flake8 这个文件。选择这种方式,而不是直接复制文件,是基于如下原因:
- 没办法预先知道 Python 解释器的版本和位置。
- 很难确定会安装在哪里。
- 无法优雅地解决可移植到不同系统上的问题。
当然你可能看到过这样的写法:
#!/usr/bin/python # EASY-INSTALL-ENTRY-SCRIPT:'flake8==2.5.1','console_scripts','flake8' __requires__='flake8==2.5.1' import sys from pkg_resources import load_entry_point if__name__=='__main__': sys.exit( load_entry_point('flake8==2.5.1', 'console_scripts', 'flake8')() )
它和上面命令的作用是一样的,只不过它是使用 easy_install 或者“python setup.py install”安装生成的,load_entry_point 可以去抽取入口点是“flake8”的键并定位到 flake8.main,然后运行 main 函数。
使用 entry_points 的优点,就是可以让这些入口点能够被其他 Python 程序动态发现包所提供的功能,但是对应的代码的耦合度非常低。
插件系统
entry_points 机制还能直接作为插件系统,比如 flake8 项目,上面提到它的 entry_points 中有这样一段:
'flake8.extension':[ 'F=flake8._pyflakes:FlakesChecker', ]
flake8 的文档中介绍了如何写一个插件(http://flake8.readthedocs.org/en/latest/extensions.html ),就是使用了 entry_points 作为插件机制。我们看一下已有的 flake8 插件之一 pep8-naming (https://github.com/PyCQA/pep8-naming )的 entry_points 的一部分(https://github.com/PyCQA/pep8-naming/blob/master/setup.py#L47 ):
'flake8.extension':[ 'N8=pep8ext_naming:NamingChecker', ]
我们先安装 pep-naming:
> pip install--user pep8-naming > flake8--version 2.5.1 (pep8:1.5.7, pyflakes:0.8.1, naming:0.3.3, mccabe:0.3.1) CPython 2.7.6 on Linux #可以看到已经出现了 naming:0.3.3
它们使用了相同的键 flake8.extension,是怎么工作的呢?看一下 flake8 的插件实现(篇幅所限,此处只截取了相关的代码):
import pep8 from pkg_resources import iter_entry_points extensions=[] def_load_entry_point(entry_point, verify_requirements): if hasattr(entry_point, 'resolve') and hasattr(entry_point, 'require'): if verify_requirements: entry_point.require() plugin=entry_point.resolve() else: plugin=entry_point.load(require=verify_requirements) return plugin for entry in iter_entry_points('flake8.extension'): checker=_load_entry_point(entry, verify_requirements=False) pep8.register_check(checker, codes=[entry.name]) extensions.add((checker.name, checker.version))
这样就实现了一个简单的插件系统。
要查看当前环境全部的入口点,可以使用 iter_entry_points(None)。
虚拟环境
Python 工程师工作中常常遇到这样的场景:系统自带的 Python 是 2.6,却需要用到 Python 2.7 中的某些特性;不同的项目之间使用不同版本的某些包,但是因为某些原因(比如有依赖冲突)却不能都升级到最新版本。
所有的包都共用一个目录,很容易出现不小心更新了项目 A 的依赖,却影响了项目 B 用到的依赖的情况。这个时候就需要对环境进行隔离,使用虚拟环境让全局的 site-packages 目录非常干净和可管理。
Python 社区中创建和管理虚拟环境的工具有 virtualenv 和 pyvenv。这些工具可以帮助你快速创建一个单独、干净的 Python 环境,你可以把所需的包安装到各自孤立的环境中。
virtualenv
先安装 virtualenv:
sudo pip install virtualenv
现在创建一个 Python 环境:
> virtualenv venv New python executable in/home/ubuntu/venv/bin/python Installing setuptools, pip, wheel...done.
virtualenv 默认会创建一个包含了 Python 可执行文件、常用的标准库、激活 virtualenv 环境的脚本的目录。
使用 source 激活 virtualenv 环境:
> source venv/bin/activate (venv)> which python #注意终端提示的改变,前面添加了“(venv)”前缀。 /home/ubuntu/venv/bin/python
可以看到已经不再使用系统环境变量中的 Python 了。如果要退出虚拟环境,可以取消激活:
(venv)> deactivate
virtualenv 定制化
virtualenv 默认只是生成一个非常标准的 Python 环境,而在实际使用中,项目都会有第三方包的依赖,会出现多个项目依赖相同的包的情况。举个例子,当项目有严格的代码规范要求和集成测试要求时,那么每个项目可能都需要 py.test、flake8 之类的基础包,通常还会添加 IPython 这样的增强交互工具。除此之外,项目还可能有初始化的需要,要求在创建虚拟环境的时候做一些额外的工作。那么,对于这种情况,就可以生成一个定制的 virtualenv 脚本。
本节我们展示一个在生成 venv 环境的同时安装 flake8 的自定义脚本。首先让 ubuntu 这个用户对 virtualenv 文件可写,方便我们直接替换:
> sudo chown ubuntu:ubuntu`which virtualenv`
生成自定义脚本的内容如下(create-venv-script.py):
import subprocess import virtualenv virtualenv_path=subprocess.check_output(['which', 'virtualenv']).strip() EXTRA_TEXT=''' def after_install(options, home_dir): subprocess.call(['{}/bin/pip'.format(home_dir), 'install', 'flake8']) ''' def main(): text=virtualenv.create_bootstrap_script(EXTRA_TEXT, python_version='2.7') print 'Updating%s'%virtualenv_path with open(virtualenv_path, 'w') as f: f.write(text) if__name__=='__main__': main()
生成定制的 virtualenv 脚本:
> python chapter2/section2/create-venv-script.py Updating/usr/local/bin/virtualenv
/usr/local/bin/virtualenv 已经被替换成定制的版本了。如果不想全局替换,可以把 virtualenv_path 换成其他路径。
现在生成一个虚拟环境,就会自动安装 flake8 了:
> virtualenv tmp New python executable in/home/ubuntu/web_develop/tmp/bin/python2.7 Also creating executable in/home/ubuntu/web_develop/tmp/bin/python Installing setuptools, pip, wheel...done. ... Installing collected packages:mccabe, pyflakes, pep8, flake8 Successfully installed flake8-2.5.4 mccabe-0.4.0 pep8-1.7.0 pyflakes-1.0.0 > ll tmp/bin/flake8 -rwxrwxr-x 1 ubuntu ubuntu 239 May 26 09:51 tmp/bin/flake8
Virtualenv 定制脚本接受 3 个扩展函数。
- extend_parser(optparse_parser):添加额外的选项。
- adjust_options(options, args):改变当前的选项。
- after_install:在默认的环境安装好之后,执行其他工作,主要通过这个函数完成定制。现在做一个更复杂的定制,实现如下需求:
- 增加一个选项,可以额外安装指定的包。
- 假如没有设置这个选项,在终端发出警告提示(warn)。
- 默认的虚拟环境都安装在指定的目录之下(~/venv)。
看一下升级之后 EXTRA_TEXT 的内容(create-venv-script_v2.py):
EXTRA_TEXT=''' ROOT_PATH='/home/ubuntu/venv' def extend_parser(parser): parser.add_option( '-r','--req', action='append', type='string', dest='reqs', help="specify additional required packages", default=[]) def adjust_options(options, args): if not args: return base_dir=args[0] args[0]=join(ROOT_PATH, base_dir) def after_install(options, home_dir): if not options.reqs: logger.warn('Warn:You maybe need specify some required packages!') for req in options.reqs: subprocess.call(['{}/bin/pip'.format(home_dir), 'install', req]) '''
adjust_options 中能直接用 join 是因为 virtualenv 初始化的时候已经设置了 join=os.path.join。
重新生成 virtualenv:
> python~/web_develop/chapter2/create-venv-script2.py Updating/usr/local/bin/virtualenv
看一下升级后的效果:
> virtualenv-h|grep req #增加的新选项 -r REQS,--req=REQS specify additional required packages > virtualenv tmp New python executable in/home/ubuntu/venv/tmp/bin/python2.7 Also creating executable in/home/ubuntu/venv/tmp/bin/python Installing setuptools, pip, wheel...done. Warn:You maybe need specify some required packages!
把 tmp 安装到了/home/ubuntu/venv 目录下,最后发出警告提示。
现在创建一个自动安装 Flake8 和 Jinja2 的虚拟环境:
> virtualenv tmp2-r flake8-r jinja2
virtualenvwrapper
virtualenvwrapper 是对 virtualenv 的功能扩展,它有如下用途:
- 用来管理全部的虚拟环境。
- 能方便地创建、删除和拷贝虚拟环境。
- 用单个命令就可以切换不同的虚拟环境。
- 可以使用 Tab 补全虚拟环境。
- 支持用户粒度的钩子支持。
使用如下方式安装:
> sudo pip install virtualenvwrapper
先初始化 virtualenvwrapper:
> export WORKON_HOME=~/venv > source/usr/local/bin/virtualenvwrapper.sh
初始化之后~/venv 目录也会添加一些用户级别的 virtualenvwrapper 的钩子模板。通常上述两行会放在 shell 的配置里面,比如~/.zshrc,这样每次登录时就自动初始化了。
初始化时给系统添加了一些虚拟环境相关的函数,比如用来创建虚拟环境的 mkvirtualenv:
> mkvirtualenv venv1 (venv1)> deactivate #退出虚拟环境的命令还是一样的
mkvirtualenv 还会添加如下 5 个项目级别的钩子模板。
- predeactivate:在虚拟环境取消激活之前执行。
- postdeactivate:在虚拟环境取消激活之后执行。
- preactivate:在虚拟环境激活之前执行。
- postactivate:在虚拟环境激活之后执行。
- get_env_details:使用 lsvirtualenv/showvirtualenv 等命令时,对于当前环境的额外钩子,可以添加虚拟环境介绍等内容。
再创建一个新的虚拟环境,并添加一个钩子,在取消激活之后输出“Deactivated!”:
> mkvirtualenv venv2 (venv2)> cat/home/ubuntu/venv/venv2/bin/postdeactivate #!/usr/bin/zsh # This hook is sourced after this virtualenv is deactivated. echo 'Deactivated!' (venv2)> deactivate Deactivated!
现在已经有两个虚拟环境了,可以使用 Tab 管理它们:
> workon<Tab>#输入 workon, 然后按 Tab venv1 venv2 > workon venv1 # 选择 venv1 (venv1)> workon venv2 # 直接使用 workon 就可以切换到 venv2 (venv2)>
其他常用的命令
- lsvirtualenv:列出全部的虚拟环境。
- showvirtualenv:列出单个虚拟环境的信息。
- rmvirtualenv:删除一个虚拟环境。
- cpvirtualenv:拷贝虚拟环境。
- allvirtualenv:对当前虚拟环境执行统一的命令。比如,要给 venv1 和 venv2 都安装 flake8,就可以用 allvirtualenv pip install flake8。
- cdvirtualenv:可以直接切换到虚拟环境的子目录里面。
(venv1)> cdvirtualenv bin (venv1)> pwd /home/ubuntu/venv/venv1/bin
- cdsitepackages:和 cdvirtualenv 同理,切换到虚拟环境的 site-packages 目录下。
- lssitepackages:列出 site-packages 目录下的目录。
用户级别的钩子脚本
用户级别的钩子脚本都在 VIRTUALENVWRAPPER_HOOK_DIR 这个变量的目录下,默认是 WORKON_HOME 变量指定的目录。
常用的钩子脚本如下。
- get_env_details:当不带参数执行 workon 时执行。
- postmkvirtualenv:在虚拟环境创建和激活之后执行。
- preactivate:在虚拟环境激活之前执行。
- postactivate:在虚拟环境激活之后执行。
- predeactivate:在虚拟环境取消激活之前执行。
- postdeactivate:在虚拟环境取消激活之后执行。
- prermvirtualenv:在虚拟环境删除之前执行。
- postrmvirtualenv:在虚拟环境删除之后执行。
更多的钩子脚本类型可以在这里找到:Per-User Customization(http://bit.ly/28ReOu6 )。
virtualenv-burrito
virtualenv-burrito(http://bit.ly/28UjSml )是一个安装、配置 virtualenv 和 virtualenvwrapper 及其依赖的傻瓜式工具。无须上面烦琐的过程,只需要一步即可:
> curl -sL https://raw.githubusercontent.com/brainsik/virtualenv-burrito/master/ virtualenv-burrito.sh|$SHELL
它自动把初始化脚本放在/home/ubuntu/.zprofile 里面,这样在下次登录之后就可以使用了。如果想立刻使用初始化脚本,可以用如下语句:
source/home/ubuntu/.venvburrito/startup.sh
startup.sh 会自动创建了~/.virtualenvs 作为 WORKON_HOME,用法和 virtualenvwrapper 是一样的。
使用如下命令即可对 virtualenv 和 virtualenvwrapper 进行更新:
> virtualenv-burrito upgrade
本书所有 Python 2 的例子都使用了这个虚拟环境。
autoenv
autoenv(http://bit.ly/290YIQG )让你在切换目录的时候可以完成自动激活虚拟环境(如果有的话)等定制操作。
先安装它:
> sudo pip install autoenv > source/usr/local/bin/activate.sh
我们需要做的就是在目录下新建一个.env 文件,然后修改这个文件,添加激活命令就好了:
> touch .env > echo"source source/home/ubuntu/.virtualenvs/venv/bin/activate"> .env
这里一定要用完整的绝对路径,否则执行 cd web_develop/subdir 之类的命令会让虚拟环境激活失败。
效果如下:
> cd #先切换到其他目录 > cd web_develop # 出现如下提示 autoenv: autoenv: WARNING: autoenv: This is the first time you are about to source/home/ubuntu/web_develop/.env: autoenv: autoenv: ---(begin contents)--------------------------------------- autoenv: source/home/ubuntu/venv/bin/activate autoenv: autoenv: ---(end contents)----------------------------------------- autoenv: autoenv: Are you sure you want to allow this? (y/N) y (venv)> #可以看到提示符的变化
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论