python模块导入-相对路径问题 - python

我正在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软件包的基本过程,其中包含了我认为是社区中的最佳实践。这是基本的-如果您真的很认真地开发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,请使用virtualenveasy_installpip软件包安装到系统范围的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软件包

现在创建您的包。基本上,您需要一个带有setuptoolssetup.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_installpip install foobar来安装您的软件包
其他软件包将使用的名称取决于您在setup.py中的名称
PyPi上将被称为什么

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

人(或您)将使用import foobarfrom 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

Python sqlite3数据库已锁定 - python

我在Windows上使用Python 3和sqlite3。我正在开发一个使用数据库存储联系人的小型应用程序。我注意到,如果应用程序被强制关闭(通过错误或通过任务管理器结束),则会收到sqlite3错误(sqlite3.OperationalError:数据库已锁定)。我想这是因为在应用程序关闭之前,我没有正确关闭数据库连接。我已经试过了: connectio…

用大写字母拆分字符串,但忽略AAA Python Regex - python

我的正则表达式:vendor = "MyNameIsJoe. I'mWorkerInAAAinc." ven = re.split(r'(?<=[a-z])[A-Z]|[A-Z](?=[a-z])', vendor) 以大写字母分割字符串,例如:'我的名字是乔。 I'mWorkerInAAAinc”变成…

字符串文字中的正斜杠表现异常 - python

为什么S1和S2在撇号位置方面表现不同?S1="1/282/03/10" S2="4/107/03/10" R1="".join({"N\'" ,S1,"\'" }) R2="".join({"N\'…

Python pytz时区函数返回的时区为9分钟 - python

由于某些原因,我无法从以下代码中找出原因:>>> from pytz import timezone >>> timezone('America/Chicago') 我得到:<DstTzInfo 'America/Chicago' LMT-1 day, 18:09:00 STD…

如何打印浮点数的全精度[Python] - python

我编写了以下函数,其中传递了x,y的值:def check(x, y): print(type(x)) print(type(y)) print(x) print(y) if x == y: print "Yes" 现在当我打电话check(1.00000000000000001, 1.0000000000000002)它正在打印:<…