我正在python 2.7中开发自己的模块。它位于~/Development/.../myModule
而不是/usr/lib/python2.7/dist-packages
或/usr/lib/python2.7/site-packages
中。内部结构为:
/project-root-dir
/server
__init__.py
service.py
http.py
/client
__init__.py
client.py
client/client.py
包括PyCachedClient
类。我遇到导入问题:project-root-dir$ python
Python 2.7.2+ (default, Jul 20 2012, 22:12:53)
[GCC 4.6.1] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from server import http
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "server/http.py", line 9, in <module>
from client import PyCachedClient
ImportError: cannot import name PyCachedClient
我没有将PythonPath设置为包含我的
project-root-dir
,因此,当server.http尝试包含client.PyCachedClient时,它将尝试从相对路径加载它并失败。我的问题是-我应该如何以良好的pythonic方式设置所有路径/设置?我知道每次打开控制台并尝试运行服务器时都可以在shell中运行export PYTHONPATH=...
,但是我猜这不是最好的方法。如果我的模块是通过PyPi安装的(或类似的东西),我会将其安装在/usr/lib/python...
路径中,并且会自动加载。我将感谢有关python模块开发中最佳实践的技巧。
最佳答案
我的Python开发工作流程
这是开发Python软件包的基本过程,其中包含了我认为是社区中的最佳实践。这是基本的-如果您真的很认真地开发Python软件包,那么还有更多的东西,每个人都有自己的喜好,但是它应该作为入门的模板,然后更多地了解其中涉及的部分。基本步骤是:
使用virtualenv
进行隔离setuptools
用于创建可安装的软件包并管理依赖项python setup.py develop
以开发模式安装该软件包
虚拟环境
首先,我建议使用virtualenv
获得隔离的环境来开发您的软件包。在开发过程中,您将需要安装,升级,降级和卸载软件包的依赖项,而您不希望
您的开发依赖关系会污染系统范围的site-packages
您整个系统的site-packages
会影响您的开发环境
版本冲突
在整个系统范围内污染site-packages
是不好的,因为即使您的小型项目只需要该依赖项,安装在该系统上的任何软件包都将对使用该系统Python的所有Python应用程序可用。并且它只是安装在新版本中,该新版本取代了系统范围site-packages
中的那个版本,并且与依赖于该版本的$ {important_app}不兼容。你明白了。
让整个系统的site-packages
影响您的开发环境是不好的,因为也许您的项目取决于系统Python的site-packages
中已经存在的模块。因此,您忘记正确地声明您的项目依赖于该模块,但是一切正常,因为它始终在本地开发箱中。在发布软件包之前,人们会尝试安装它或将其投入生产等。在干净的环境中进行开发会迫使您正确声明依赖项。
因此,virtualenv是一个隔离的环境,具有自己的Python解释器和模块搜索路径。它基于您先前安装但与之隔离的Python安装。
要创建virtualenv,请使用virtualenv
或easy_install
将pip
软件包安装到系统范围的Python中,以进行安装:
sudo pip install virtualenv
请注意,这是您唯一一次以root用户身份(使用sudo)在全局站点程序包中安装某些程序。之后的所有操作都将在您要创建的virtualenv内部进行。
现在创建一个virtualenv来开发您的软件包:
cd ~/pyprojects
virtualenv --no-site-packages foobar-env
这将创建一个目录树
~/pyprojects/foobar-env
,即您的virtualenv。要激活virtualenv,请在其中
cd
并在source
bin/activate script
中激活:~/pyprojects $ cd foobar-env/
~/pyprojects/foobar-env $ . bin/activate
(foobar-env) ~/pyprojects/foobar-env $
注意前导点
.
,这是source
shell命令的简写。还要注意提示如何更改:(foobar-env)
表示您在激活的virtualenv内部(并且始终需要隔离才能起作用)。因此,每次您打开新的终端标签或SSH会话等时,请激活您的环境。如果现在在已激活的环境中运行
python
,它将实际使用~/pyprojects/foobar-env/bin/python
作为解释器,并具有自己的site-packages
和隔离的模块搜索路径。setuptools软件包
现在创建您的包。基本上,您需要一个带有
setuptools
的setup.py
软件包,以正确声明您的软件包的元数据和依赖项。您可以通过遵循setuptools documentation自行执行此操作,也可以使用Paster templates创建程序包骨架。要使用Paster模板,请在您的virtualenv中安装PasteScript
:pip install PasteScript
让我们为新程序包创建一个源目录,以使事情井井有条(也许您想将您的项目分成几个程序包,或者稍后使用源程序中的依赖项):
mkdir src
cd src/
现在创建您的包,执行
paster create -t basic_package foobar
并在交互式界面中回答所有问题。大多数是可选的,只需按ENTER即可保留默认值。
这将创建一个名为
foobar
的软件包(或更准确地说,是setuptools发行版)。这是那个名字人们将使用
easy_install
或pip install foobar
来安装您的软件包其他软件包将使用的名称取决于您在
setup.py
中的名称PyPi上将被称为什么
在内部,您几乎总是创建一个称为相同的Python包(例如在带有
__init__.py
的目录中)。这不是必需的,顶级Python包的名称可以是任何有效的包名称,但这是一个通用约定将其命名为与发行版相同。这就是为什么将两者分开很重要但并不总是那么容易的原因,因为顶级python软件包名称是什么人(或您)将使用
import foobar
或from foobar import baz
导入软件包因此,如果您使用粘贴模板,它将已经为您创建了该目录:
cd foobar/foobar/
现在创建您的代码:
vim models.py
models.py
class Page(object):
"""A dumb object wrapping a webpage.
"""
def __init__(self, content, url):
self.content = content
self.original_url = url
def __repr__(self):
return "<Page retrieved from '%s' (%s bytes)>" % (self.original_url, len(self.content))
和使用
client.py
的同一目录中的models.py
:client.py
import requests
from foobar.models import Page
url = 'http://www.stackoverflow.com'
response = requests.get(url)
page = Page(response.content, url)
print page
在
requests
中声明对setup.py
模块的依赖关系: install_requires=[
# -*- Extra requirements: -*-
'setuptools',
'requests',
],
版本控制
src/foobar/
是您现在要置于版本控制下的目录:cd src/foobar/
git init
vim .gitignore
.gitignore
*.egg-info
*.py[co]
git add .
git commit -m 'Create initial package structure.
将软件包作为开发工具安装
现在是时候以开发模式安装软件包了:
python setup.py develop
这将安装
requests
依赖项和您的软件包作为开发工具。因此,它已链接到您的virtualenv的站点程序包中,但仍位于src/foobar
上,您可以在其中进行更改,并使它们立即在virtualenv中处于活动状态,而无需重新安装程序包。现在,对于您的原始问题,使用相对路径导入:我的建议是,不要这样做。现在您已经有了一个正确的setuptools程序包,该程序包已安装并且可以导入,当前的工作目录不再重要。只需执行
from foobar.models import Page
或类似方法,即可声明该对象所在位置的完全限定名称。对于您自己和其他阅读您代码的人来说,这使您的源代码更具可读性和可发现性。现在,您可以通过在激活的virtualenv内的任何位置执行
python client.py
来运行代码。 python src/foobar/foobar/client.py
可以正常工作,您的软件包已正确安装,您的工作目录也不再重要。如果您想更进一步,甚至可以为CLI脚本创建setuptools入口点。这将在您的virtualenv中创建一个
bin/something
脚本,您可以从shell中运行它。setuptools console_scripts入口点
setup.py
entry_points='''
# -*- Entry points: -*-
[console_scripts]
run-fooobar = foobar.main:run_foobar
''',
client.py
def run_client():
# ...
main.py
from foobar.client import run_client
def run_foobar():
run_client()
重新安装软件包以激活入口点:
python setup.py develop
然后,
bin/run-foo
。一旦您(或其他人)在virtualenv之外真正安装了软件包,入口点将位于
/usr/local/bin/run-foo
或类似的位置,而该位置将自动位于$PATH
中。进一步的步骤
创建软件包的发行版并将其上传到PyPi,例如使用
zest.releaser
保留变更日志并对软件包进行版本控制
了解有关declaring dependencies
了解有关Differences between distribute, distutils, setuptools and distutils2
建议阅读:
The Hitchhiker’s Guide to Packaging
The pip cookbook