Как сделать относительный импорт в Python?

528

Представьте себе эту структуру каталогов:

app/
   __init__.py
   sub1/
      __init__.py
      mod1.py
   sub2/
      __init__.py
      mod2.py

Я кодирую mod1, и мне нужно импортировать что-то из mod2. как мне это сделать?

Я пытался, from ..sub2 import mod2но я получаю "Попытка относительного импорта в не-пакет".

Я погуглил, но нашел только " sys.pathманипулятивные" хаки. Разве нет чистого пути?


Изменить: все мои __init__.pyв настоящее время пустые

Edit2: я пытаюсь сделать это, потому что sub2 содержит классы, которые являются общими для подпакетов ( sub1, subXи т. Д.).

Edit3: поведение, которое я ищу, такое же, как описано в PEP 366 (спасибо Джон B)

Joril
источник
8
Я рекомендую обновить ваш вопрос, чтобы было более понятно, что вы описываете проблему, описанную в PEP 366.
Джон Б.
2
Это длинное объяснение, но проверьте здесь: stackoverflow.com/a/10713254/1267156 Я ответил на очень похожий вопрос. У меня была такая же проблема до прошлой ночи.
Sevvy325
3
Для тех, кто хочет загрузить модуль, расположенный по произвольному пути, смотрите это: stackoverflow.com/questions/67631/…
Евгений Сергеев
2
В соответствующей заметке Python 3 изменит обработку по умолчанию для импорта на абсолютную по умолчанию; относительный импорт должен быть указан явно.
Росс

Ответы:

337

Кажется, что каждый хочет сказать вам, что вы должны делать, а не просто отвечать на вопрос.

Проблема в том, что вы запускаете модуль как «__main__», передавая mod1.py в качестве аргумента интерпретатору.

От PEP 328 :

Относительный импорт использует атрибут модуля __name__, чтобы определить позицию этого модуля в иерархии пакетов. Если имя модуля не содержит никакой информации о пакете (например, для него задано значение «__main__»), то относительный импорт разрешается так, как если бы этот модуль был модулем верхнего уровня, независимо от того, где этот модуль фактически расположен в файловой системе.

В Python 2.6 они добавляют возможность ссылаться на модули относительно основного модуля. PEP 366 описывает изменение.

Обновление : по словам Ника Коглана, рекомендуемая альтернатива - запустить модуль внутри пакета с помощью ключа -m.

Джон Б
источник
2
Ответ здесь включает использование sys.path в каждой точке входа в вашу программу. Я думаю, это единственный способ сделать это.
Ник Реталлак
76
Рекомендуемая альтернатива - запускать модули внутри пакетов, используя -mпереключатель, а не указав их имя файла напрямую.
ncoghlan
127
Я не понимаю: где ответ здесь? Как импортировать модули в такую ​​структуру каталогов?
Том
27
@Tom: В этом случае, мод1 будет from sub2 import mod2. Затем, чтобы запустить mod1, из приложения сделайте python -m sub1.mod1.
Xiong Chiamiov
11
@XiongChiamiov: означает ли это, что вы не можете сделать это, если ваш python встроен в приложение, поэтому у вас нет доступа к переключателям командной строки python?
LarsH
130

Вот решение, которое работает для меня:

Я делаю относительный импорт как, from ..sub2 import mod2 а затем, если я хочу запустить, mod1.pyя иду в родительский каталог appи запускаю модуль, используя ключ python -m as python -m app.sub1.mod1.

Реальная причина, по которой эта проблема возникает при относительном импорте, заключается в том, что относительный импорт работает, беря __name__свойство модуля. Если модуль запускается напрямую, то __name__он установлен на __main__и не содержит никакой информации о структуре пакета. И именно поэтому Python жалуется на relative import in non-packageошибку.

Таким образом, с помощью ключа -m вы предоставляете информацию о структуре пакета в python, с помощью которой он может успешно разрешить относительный импорт.

Я сталкивался с этой проблемой много раз при выполнении относительного импорта. И, прочитав все предыдущие ответы, я все еще не мог понять, как решить это, чистым способом, без необходимости помещать шаблонный код во все файлы. (Хотя некоторые комментарии были действительно полезны, благодаря @ncoghlan и @XiongChiamiov)

Надеюсь, что это поможет кому-то, кто борется с проблемой относительного импорта, потому что проходить PEP на самом деле не весело.

Панкай
источник
9
Лучший ответ ИМХО: не только объясняет, почему у ОП возникла проблема, но и находит способ ее решения, не меняя способ импорта своих модулей . В конце концов, относительный импорт OP был в порядке. Виновником было отсутствие доступа к внешним пакетам при непосредственном запуске в виде скрипта, что-то -mбыло разработано для решения проблемы.
МестреЛион
26
Также обратите внимание: этот ответ был через 5 лет после вопроса. Эти функции не были доступны в то время.
JeremyKun
1
Если вы хотите импортировать модуль из той же директории, вы можете это сделать from . import some_module.
Ротарети
124
main.py
setup.py
app/ ->
    __init__.py
    package_a/ ->
       __init__.py
       module_a.py
    package_b/ ->
       __init__.py
       module_b.py
  1. Ты бежишь python main.py.
  2. main.py делает: import app.package_a.module_a
  3. module_a.py делает import app.package_b.module_b

В качестве альтернативы 2 или 3 можно использовать: from app.package_a import module_a

Это будет работать до тех пор, пока у вас есть appPYTHONPATH. main.pyможет быть где угодно

Таким образом, вы пишете setup.pyдля копирования (установки) всего пакета приложения и подпакетов в папки python main.pyцелевой системы и в папки сценариев целевой системы.

nosklo
источник
3
Отличный ответ. Есть ли способ импортировать этот путь без установки пакета в PYTHONPATH?
Авраам
4
Предлагаемое дополнительное чтение: blog.habnab.it/blog/2013/07/21/python-packages-and-you
nosklo
6
Затем, однажды, нужно изменить имя приложения на test_app. что случилось бы? Вам нужно будет изменить все исходные коды, импортировать app.package_b.module_b -> test_app.package_b.module_b. это абсолютно ПЛОХАЯ практика ... И мы должны попытаться использовать относительный импорт внутри пакета.
Spybdai
49

«Гвидо рассматривает выполнение сценариев внутри пакета как анти-шаблон» (отклонено PEP-3122 )

Я потратил так много времени, пытаясь найти решение, читая соответствующие посты здесь, в Stack Overflow и говоря себе: «Должен быть лучший способ!». Похоже, нет.

Лесник
источник
10
Примечание. Уже упоминавшийся pep-366 (созданный примерно в то же время, что и pep-3122 ) предоставляет те же возможности, но использует другую обратно совместимую реализацию, т. Е. Если вы хотите запустить модуль внутри пакета как скрипт и использовать явный относительный импорт тогда вы можете запустить его с помощью -mswitch: python -m app.sub1.mod1или вызвать app.sub1.mod1.main()из скрипта верхнего уровня (например, сгенерированного из точек входа setuptools, определенных в setup.py).
JFS
+1 для использования setuptools и точек входа - это правильный способ настроить сценарии, которые будут запускаться извне, в четко определенном месте, в отличие от бесконечного взлома PYTHONPATH
RecencyEffect
38

Это решено на 100%:

  • приложение/
    • main.py
  • настройки /
    • local_setings.py

Импортируйте настройки / local_setting.py в app / main.py:

main.py:

import sys
sys.path.insert(0, "../settings")


try:
    from local_settings import *
except ImportError:
    print('No Import')
Роман Арсеньев
источник
2
благодарю вас! все люди заставляли меня запускать мой скрипт по-другому, вместо того, чтобы рассказывать, как решить его в скрипте. Но я должен был изменить код, чтобы использовать, sys.path.insert(0, "../settings")а затемfrom local_settings import *
Вит Бернатик
25
def import_path(fullpath):
    """ 
    Import a file with full path specification. Allows one to
    import from anywhere, something __import__ does not do. 
    """
    path, filename = os.path.split(fullpath)
    filename, ext = os.path.splitext(filename)
    sys.path.append(path)
    module = __import__(filename)
    reload(module) # Might be out of date
    del sys.path[-1]
    return module

Я использую этот фрагмент для импорта модулей из путей, надеюсь, это поможет

iElectric
источник
2
Я использую этот фрагмент, в сочетании с модулем Imp (как описано здесь [1]) для большого эффекта. [1]: stackoverflow.com/questions/1096216/…
Сюн Чямов
7
Вероятно, sys.path.append (path) следует заменить на sys.path.insert (0, path), а sys.path [-1] следует заменить на sys.path [0]. В противном случае функция импортирует неправильный модуль, если в пути поиска уже есть модуль с таким именем. Например, если в текущем каталоге есть «some.py», import_path («/ import / some.py») импортирует не тот файл.
Алекс Че
Согласен! Иногда другой относительный импорт будет иметь преимущество. Используйте sys.path.insert
iElectric
Как бы вы повторили поведение из x import y (или *)?
Levesque
Это не ясно, пожалуйста, укажите полное использование этого сценария для решения проблемы ОП.
mrgloom
21

объяснение nosklo'sответа примерами

примечание: все __init__.pyфайлы пусты.

main.py
app/ ->
    __init__.py
    package_a/ ->
       __init__.py
       fun_a.py
    package_b/ ->
       __init__.py
       fun_b.py

Приложение / package_a / fun_a.py

def print_a():
    print 'This is a function in dir package_a'

Приложение / package_b / fun_b.py

from app.package_a.fun_a import print_a
def print_b():
    print 'This is a function in dir package_b'
    print 'going to call a function in dir package_a'
    print '-'*30
    print_a()

main.py

from app.package_b import fun_b
fun_b.print_b()

если вы запустите $ python main.pyэто возвращает:

This is a function in dir package_b
going to call a function in dir package_a
------------------------------
This is a function in dir package_a
  • main.py делает: from app.package_b import fun_b
  • fun_b.py делает from app.package_a.fun_a import print_a

поэтому файл в папке package_bиспользуется файл в папке package_a, что вы хотите. Правильно??

suhailvs
источник
12

К сожалению, это взлом sys.path, но он работает довольно хорошо.

Я столкнулся с этой проблемой в другом слое: у меня уже был модуль с указанным именем, но это был не тот модуль.

я хотел сделать следующее (модуль, из которого я работал, был module3):

mymodule\
   __init__.py
   mymodule1\
      __init__.py
      mymodule1_1
   mymodule2\
      __init__.py
      mymodule2_1


import mymodule.mymodule1.mymodule1_1  

Обратите внимание, что я уже установил mymodule, но в моей установке у меня нет "mymodule1"

и я получил бы ImportError, потому что он пытался импортировать из моих установленных модулей.

Я попытался сделать sys.path.append, и это не сработало. То, что сработало, было sys.path.insert

if __name__ == '__main__':
    sys.path.insert(0, '../..')

Так что вроде хак, но заставил все это работать! Так что имейте в виду, что если вы хотите, чтобы ваше решение переопределило другие пути, вам нужно использовать sys.path.insert (0, pathname), чтобы заставить его работать! Для меня это было очень неприятным камнем преткновения, многие говорят, что используют функцию «добавить» в sys.path, но это не работает, если у вас уже есть определенный модуль (я нахожу это очень странным поведением)

Гаррет Берг
источник
sys.path.append('../')отлично работает для меня (Python 3.5.2)
Nister
Я думаю, что это нормально, поскольку он локализует взлом исполняемого файла и не влияет на другие модули, которые могут зависеть от ваших пакетов.
Том Рассел
10

Позвольте мне поставить это здесь для моей собственной справки. Я знаю, что это не очень хороший код Python, но мне нужен был скрипт для проекта, над которым я работал, и я хотел поместить скрипт в scriptsкаталог.

import os.path
import sys
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
milkypostman
источник
9

Как говорит @EvgeniSergeev в комментариях к ОП, вы можете импортировать код из .pyфайла в произвольном месте с помощью:

import imp

foo = imp.load_source('module.name', '/path/to/file.py')
foo.MyClass()

Это взято из этого SO ответа .

LondonRob
источник
2

Из документа Python ,

В Python 2.5 вы можете переключить поведение импорта на абсолютный импорт с помощью from __future__ import absolute_importдирективы. Такое поведение абсолютного импорта станет будущим по умолчанию в будущей версии (возможно, Python 2.7). Как только абсолютный импорт по умолчанию, import stringвсегда найдет версию стандартной библиотеки. Предполагается, что пользователи должны начать использовать абсолютный импорт как можно больше, поэтому предпочтительно начинать писать from pkg import stringв своем коде

Юнг Роу
источник
1

Я обнаружил, что проще установить переменную окружения "PYTHONPATH" в верхнюю папку:

bash$ export PYTHONPATH=/PATH/TO/APP

тогда:

import sub1.func1
#...more import

Конечно, PYTHONPATH "глобальный", но это не доставило мне проблем.

Andrew_1510
источник
По сути, это virtualenvпозволяет вам управлять вашими операциями импорта.
17
1

Вдобавок к тому, что сказал Джон Б., похоже, что установка __package__переменной должна помочь, а не менять, __main__что может испортить другие вещи. Но, насколько я мог проверить, он работает не совсем так, как должен.

У меня та же проблема, и ни PEP 328, ни 366 не решают ее полностью, так как оба к концу дня нуждаются в том, чтобы глава пакета была включена в sys.path , насколько я понимаю.

Я должен также упомянуть, что я не нашел, как отформатировать строку, которая должна идти в эти переменные. Это "package_head.subfolder.module_name"или что?

Габриель
источник
0

Вы должны добавить путь к модулю PYTHONPATH:

export PYTHONPATH="${PYTHONPATH}:/path/to/your/module/"
Гиоргос Мириантос
источник
1
Это примерно то же самое, что манипулирование sys.path, так как sys.pathинициализируется сPYTHONPATH
Joril
@Joril Это правильно, но его sys.pathнеобходимо жестко закодировать в исходном коде, в отличие от того, PYTHONPATHкоторый является переменной окружения и может быть экспортирован.
Гиоргос Мириантус