19

我正在 python 2.7 中开发自己的模块。它驻留在~/Development/.../myModule而不是/usr/lib/python2.7/dist-packagesor /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 设置为包含 my project-root-dir,因此当 server.http 尝试包含 client.PyCachedClient 时,它会尝试从相对路径加载它并失败。我的问题是 - 我应该如何以一种好的 Python 方式设置所有路径/设置?我知道export PYTHONPATH=...每次打开控制台并尝试运行服务器时我都可以在 shell 中运行,但我想这不是最好的方法。如果我的模块是通过 PyPi(或类似的东西)安装的,我会将它安装在/usr/lib/python...路径中并自动加载。

我很欣赏有关 python 模块开发最佳实践的提示。

4

2 回答 2

42

我的 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将其安装到系统范围的 Python 来安装包:easy_installpip

sudo pip install virtualenv

请注意,这将是您以 root 身份(使用 sudo)将某些东西安装到您的全局站点包中的唯一一次。之后的一切都将发生在您即将创建的 virtualenv 中。

现在创建一个 virtualenv 来开发你的包:

cd ~/pyprojects
virtualenv --no-site-packages foobar-env

这将创建一个目录树~/pyprojects/foobar-env,即您的 virtualenv。

要激活 virtualenv,cd进入它sourcebin/activate script

~/pyprojects $ cd foobar-env/
~/pyprojects/foobar-env $ . bin/activate
(foobar-env) ~/pyprojects/foobar-env $

注意前面的点.,它是sourceshell 命令的简写。还要注意提示的变化:(foobar-env)意味着你在激活的 virtualenv 中(并且总是需要隔离才能工作)。因此,每次打开新的终端选项卡或 SSH 会话等时都要激活您的环境。

如果您现在python在该激活的环境中运行,它实际上将~/pyprojects/foobar-env/bin/python用作解释器,具有自己的site-packages和隔离的模块搜索路径。

安装工具包

现在用于创建您的包。基本上,您需要一个setuptools带有 a 的包setup.py来正确声明包的元数据和依赖项。您可以按照setuptools 文档自行执行此操作,或使用Paster 模板创建包骨架。要使用 Paster 模板,请安装PasteScript到您的 virtualenv 中:

pip install PasteScript

让我们为我们的新包创建一个源目录以使事情井井有条(也许您希望将项目拆分为多个包,或者稍后使用源中的依赖项):

mkdir src
cd src/

现在创建你的包,做

paster create -t basic_package foobar

并回答交互式界面中的所有问题。大多数是可选的,只需按 ENTER 即可保留默认值。

这将创建一个名为foobar. 这是这个名字

  • 人们将使用easy_installpip install foobar
  • 其他包将用于依赖于您的名称setup.py
  • 在PyPi上会叫什么

在内部,您几乎总是创建一个名为相同的 Python 包(如在“带有 的目录中__init__.py)。这不是必需的,顶级 Python 包的名称可以是任何有效的包名称,但命名它是一种通用约定与发行版相同。这就是为什么将两者分开很重要但并不总是那么容易的原因。因为顶级python包名称是什么

  • 人们(或您)将使用import foobarfrom foobar import baz

因此,如果您使用了 paste 模板,它已经为您创建了该目录:

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或类似操作,声明该对象所在的完全限定名称。对于您自己和其他阅读您的代码的人来说,这使您的源代码更具可读性和可发现性。

您现在可以通过python client.py在您激活的 virtualenv 中的任何地方运行您的代码。python src/foobar/foobar/client.py工作正常,您的软件包已正确安装,您的工作目录不再重要。

如果您想更进一步,您甚至可以为您的 CLI 脚本创建一个 setuptools 入口点。这将在您的 virtualenv 中创建一个bin/something脚本,您可以从 shell 运行该脚本。

setuptools 控制台脚本入口点

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.

进一步的步骤

推荐阅读:

于 2013-11-09T15:12:19.430 回答
2

So, you have two packages, the first with modules named:

server         # server/__init__.py
server.service # server/service.py
server.http    # server/http.py

The second with modules names:

client         # client/__init__.py
client.client  # client/client.py

If you want to assume both packages are in you import path (sys.path), and the class you want is in client/client.py, then in you server you have to do:

from client.client import PyCachedClient

You asked for a symbol out of client, not client.client, and from your description, that isn't where that symbol is defined.

I personally would consider making this one package (ie, putting an __init__.py in the folder one level up, and giving it a suitable python package name), and having client and server be sub-packages of that package. Then (a) you could do relative imports if you wanted to (from ...client.client import something), and (b) your project would be more suitable for redistribution, not putting two very generic package names at the top level of the python module hierarchy.

于 2013-11-09T15:05:27.377 回答