Импорт модуля 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, он пытается загрузить его из относительного пути и терпит неудачу. Мой вопрос: как я должен установить все пути/настройки в хорошем, питоническом ключе? Я знаю, что могу запускать export PYTHONPATH=... в оболочке каждый раз, когда открываю консоль и пытаюсь запустить свой сервер, но я думаю, что это не лучший способ. Если мой модуль был установлен через PyPi (или что-то подобное), я бы установил его в путь /usr/lib/python..., и он будет загружен автоматически.

Я был бы признателен за советы по лучшим методам разработки модуля python.

Ответ 1

Рабочий процесс разработки My Python

Это основной процесс разработки пакетов Python, который включает в себя то, что, по моему мнению, является наилучшей практикой в ​​сообществе. Это основное - если вы действительно серьезно относитесь к разработке пакетов Python, все еще немного больше, и у каждого есть свои предпочтения, но он должен служить шаблоном для начала работы, а затем больше узнать о связанных с ним частях. Основные шаги:

  • Используйте virtualenv для изоляции
  • setuptools для создания устанавливаемого пакета и управления зависимостями
  • python setup.py develop для установки этого пакета в режиме разработки

virtualenv

Во-первых, я бы рекомендовал использовать 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_install или pip:

sudo pip install virtualenv

Обратите внимание, что это будет единственный раз, когда вы установите что-то как root (используя sudo) в свои глобальные пакеты сайтов. Все после этого произойдет внутри виртуального виртуального пространства, которое вы собираетесь создать.

Теперь создайте 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. Также обратите внимание, как изменяется приглашение: (foobar-env) означает, что вы внутри активированного virtualenv (и всегда должны быть изолированы для работы). Поэтому активируйте свой env каждый раз, когда вы открываете новую вкладку терминала или сеанс SSH и т.д.

Если вы запустили python в активированном env, он фактически будет использовать ~/pyprojects/foobar-env/bin/python в качестве интерпретатора со своим собственным site-packages и изолированным поиском пути.

Пакет setuptools

Теперь для создания вашего пакета. В основном вам понадобится пакет setuptools с setup.py, чтобы правильно объявить метаданные и зависимости вашего пакета. Вы можете сделать это самостоятельно, следуя документации setuptools или создайте скелет пакета, используя Пастельные шаблоны. Чтобы использовать шаблоны Paster, установите PasteScript в свой virtualenv:

pip install PasteScript

Позвольте создать исходный каталог для нашего нового пакета, чтобы все было организовано (возможно, вы захотите разделить проект на несколько пакетов или позже использовать зависимости из исходного кода):

mkdir src
cd src/

Теперь для создания вашего пакета выполните

paster create -t basic_package foobar

и ответьте на все вопросы в интерактивном интерфейсе. Большинство из них являются необязательными и могут быть просто оставлены по умолчанию, нажимая ENTER.

Это создаст пакет (или, точнее, дистрибутив setuptools) под названием foobar. Это имя, которое

  • люди будут использовать для установки вашего пакета с помощью easy_install или pip install foobar
  • имя других пакетов будет использоваться в зависимости от вашего в setup.py
  • то, что он будет называть PyPi

Внутри вы почти всегда создаете пакет Python (как в "каталоге с __init__.py)). Это не требуется, имя пакета Python верхнего уровня может быть любым допустимым именем пакета, но оно общее соглашение, чтобы называть его таким же, как и распределение. И поэтому важно, но не всегда легко, сохранить эти два. Поскольку имя пакета python верхнего уровня - это то, что

  • люди (или вы) будут использовать для импорта вашего пакета с помощью import foobar или from foobar import baz

Итак, если вы использовали шаблон paster, он уже создал для вас этот каталог:

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))

И a 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 и ваш пакет как яйцо разработки. Поэтому он связан с вашими виртуальными сайтами-пакетами, но все еще живет в src/foobar, где вы можете вносить изменения и немедленно активировать их в virtualenv без повторной установки вашего пакета.

Теперь для вашего первоначального вопроса, импортируя с использованием относительных путей: Мой совет: не делайте этого. Теперь, когда у вас есть правильный пакет setuptools, который установлен и импортирован, ваш текущий рабочий каталог больше не имеет значения. Просто сделайте from foobar.models import Page или подобное, объявив полное имя, в котором живет этот объект. Это делает ваш исходный код более читабельным и доступным для себя и других людей, которые читают ваш код.

Теперь вы можете запустить свой код, выполнив python client.py из любого места внутри вашего активированного виртуального сервера. python src/foobar/foobar/client.py работает так же хорошо, ваш пакет правильно установлен, и ваш рабочий каталог больше не имеет значения.

Если вы хотите сделать еще один шаг, вы даже можете создать точку входа setuptools для своих сценариев CLI. Это создаст bin/something script в вашем virtualenv, который вы можете запустить из оболочки.

параметр 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 или где-нибудь в simiar, где она будет автоматически находиться в $PATH.

Дальнейшие шаги

Рекомендуемое чтение:

Ответ 2

Итак, у вас есть два пакета, первый с модулями с именем:

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

Вторая с именами модулей:

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

Если вы хотите предположить, что оба пакета находятся в пути импорта (sys.path), а нужный класс находится в client/client.py, то на вашем сервере вы должны сделать:

from client.client import PyCachedClient

Вы запросили символ из client, а не client.client и из вашего описания, а не там, где этот символ определен.

Я лично подумал бы о создании этого пакета (т.е. помещая __init__.py в папку на один уровень вверх и давая ему подходящее имя пакета python) и имея client и server подпакеты это упаковка. Затем (а) вы можете сделать относительный импорт, если хотите (from ...client.client import something), и (b) ваш проект будет более подходящим для перераспределения, а не для размещения двух очень общих имен пакетов на верхнем уровне иерархии модулей python.