如何让macports的python优先使用自己的包而非其他安装的包?
我花了好几天时间在Mac OS Lion上折腾,想要搭建一个科学计算的Python环境。我试过SciPack Superpack,也尝试过用pip和easy_install手动安装,但在导入或使用各种模块时还是遇到了错误。根据这个Stackoverflow的讨论,我决定用MacPorts重新安装一遍。
但是,当我运行MacPorts的Python时,它却忽略了MacPorts安装的包,反而试图从旧的安装中加载不兼容的包。我非常确定我在运行新安装的MacPorts Python。我检查了符号链接,也查看了python_select,并且是直接输入新安装的路径来启动Python的。但是当我尝试导入,比如说statsmodels时,它却从另一个目录拉取了旧版本。
这是sys.path的内容(为了简洁已做编辑):
['',
'/Library/Python/2.7/site-packages/mrjob-0.4.3_dev-py2.7.egg',
'/Library/Python/2.7/site-packages/statsmodels-0.6.0-py2.7-macosx-10.9-intel.egg',
...
'/System/Library/Frameworks/Python.framework/Versions/2.7/Extras/lib/python',
'/Library/Python/2.7/site-packages/twilio-3.6.6-py2.7.egg',
'/Library/Python/2.7/site-packages/six-1.6.1-py2.7.egg',
'/Library/Python/2.7/site-packages/httplib2-0.9-py2.7.egg',
'/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python27.zip',
'/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7',
'/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/plat-darwin',
'/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/plat-mac',
'/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/plat-mac/lib-scriptpackages',
'/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/lib-tk',
'/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/lib-old',
'/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/readline',
'/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/lib-dynload',
'/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages',
'/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/PyObjC',
'/Library/Python/2.7/site-packages']
MacPorts的安装在/opt/local,而旧的安装在/Library/Python。正如你所看到的,旧的包在列表中排得更靠前,这意味着它们的优先级更高。
环境变量PYTHONPATH是空的。如果我在PYTHONPATH中放入任何东西,它会在/Library条目之后但在/opt条目之前出现在sys.path中。所以这并没有解决问题。
如果我用-S选项启动新的Python,sys.path变成:
['',
'/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python27.zip',
'/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/',
'/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/plat-darwin',
'/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/plat-mac',
'/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/plat-mac/lib-scriptpackages',
'/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/lib-tk',
'/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/lib-old',
'/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/lib-dynload']
这样可以成功去掉那些外来的站点包条目,但同时也会删除MacPorts的站点包条目,这样我就无法加载任何东西了。
我认为问题的根源在于一个叫做/Library/Python/2.7/site-packages/easy-install.pth的文件,里面的内容如下(同样为了简洁已做编辑):
import sys; sys.__plen = len(sys.path)
./mrjob-0.4.3_dev-py2.7.egg
...
./statsmodels-0.6.0-py2.7-macosx-10.9-intel.egg
...
/System/Library/Frameworks/Python.framework/Versions/2.7/Extras/lib/python
./twilio-3.6.6-py2.7.egg
./six-1.6.1-py2.7.egg
./httplib2-0.9-py2.7.egg
import sys; new=sys.path[sys.__plen:]; del sys.path[sys.__plen:]; p=getattr(sys,'__egginsert',0); sys.path[p:p]=new; sys.__egginsert = p+len(new)
如果我在启动MacPorts Python之前重命名这个文件,Python就不会再把这些外来的包添加到它的sys.path中了。现在sys.path看起来和使用"python -S"选项时很相似,只是最后有这些额外的条目:
'/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages',
'/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/PyObjC',
'/Library/Python/2.7/site-packages'
现在我可以加载MacPorts自己的包,比如说statsmodels。通过检查statsmodels.__file__,我确认它导入的是本地包,而不是外来的包。
不过,我觉得这只是一个变通办法,而不是一个真正的解决方案。真正的解决方案应该让它按预期工作,也就是:从/opt/local/...安装启动的MacPorts Python,应该优先考虑在/opt/local/...目录树中通过MacPorts安装的包,只有在本地没有模块时才去其他地方查找。所以我希望/opt/local的条目在sys.path中排在前面,而/Library的条目排在后面。
这似乎应该是默认行为,我在Stackoverflow上看到很多评论都说MacPorts Python就是这样工作的。那么……我该怎么做才能实现这个呢?
1 个回答
你遇到的问题有两个主要原因。第一个原因是,非系统的 OS X 框架版本的 Python 2.7,比如 MacPorts 的 Python 2.7,故意把系统 Python 的 site-packages
位置,也就是 /Library/Python/2.7/site-packages
,放到了 sys.path
中,通常是在 sys.path
的最后面,也就是在这个版本自己的 site-packages
目录之后。这在 Python 的开发中一直有争论,现在 MacPorts 也有一个问题在讨论要把它去掉,理由是最好把系统 Python 和 MacPorts 的 Python 完全分开。
第二个原因是,原来的 setuptools
包和它的 easy_install
命令在 OS X 中的表现。正如你发现的,它通过一些魔法手段,利用 .pth
文件(包括 easy-install.pth
),对 sys.path
进行了一些复杂的操作,确保通过 easy_install
安装的包会优先出现在 sys.path
中,从而覆盖其他已安装的同类包。你也发现,删除 /Library/Python/2.7/site-packages
中的 easy-install.pth
文件是去掉这种行为的一种方法,而且是最简单的方法。这是基于你不想使用系统 Python 中安装的那些包。
为了避免将来再出现这个问题,长期的策略是确保你不使用 Apple 提供的 easy_install
或 easy_install-2.7
命令,这些命令在 /usr/bin
中。它们会把包安装到 /Library/Python
,供系统 Python 使用,并会创建或更新 easy-install.pth
。一般来说,你应该尽量避免使用 easy_install
。它的现代替代品是 pip
,它提供了更好的控制,并避免了与 .pth
文件的复杂操作。如果 MacPorts 还没有提供你想安装的 Python 包,通常是 py27-xxxx
这种形式,你可以安装并使用 MacPorts 的 py27-pip
,而不是使用 easy_install
:
/opt/local/bin/pip-2.7 install xxxx
你还可以使用常见的 MacPorts 功能,比如 port select pip pip27
。