Как сделать относительный импорт в Python?
Представьте себе эту структуру каталогов:
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)
21 ответ
Кажется, что каждый хочет сказать вам, что вы должны делать, а не просто отвечать на вопрос.
Проблема в том, что вы запускаете модуль как "__main__", передавая mod1.py в качестве аргумента интерпретатору.
От PEP 328:
Относительный импорт использует атрибут модуля __name__ для определения позиции этого модуля в иерархии пакетов. Если имя модуля не содержит никакой информации о пакете (например, для него задано значение "__main__"), то относительный импорт разрешается так, как если бы модуль был модулем верхнего уровня, независимо от того, где этот модуль фактически расположен в файловой системе.
В Python 2.6 они добавляют возможность ссылаться на модули относительно основного модуля. PEP 366 описывает изменение.
Обновление: по словам Ника Коглана, рекомендуемой альтернативой является запуск модуля внутри пакета с помощью ключа -m.
Вот решение, которое работает для меня:
Я делаю относительный импорт как from ..sub2 import mod2
а потом, если я захочу бежать mod1.py
тогда я иду в родительский каталог app
и запустите модуль, используя ключ python -m как python -m app.sub1.mod1
,
Реальная причина, почему эта проблема возникает с относительным импортом, заключается в том, что относительный импорт работает, принимая __name__
свойство модуля. Если модуль запускается напрямую, то __name__
установлен в __main__
и он не содержит никакой информации о структуре пакета. И именно поэтому Python жалуется на relative import in non-package
ошибка.
Таким образом, используя ключ -m, вы предоставляете информацию о структуре пакета в python, с помощью которой он может успешно разрешить относительный импорт.
Я сталкивался с этой проблемой много раз при выполнении относительного импорта. И, прочитав все предыдущие ответы, я все еще не мог понять, как решить это, чистым способом, без необходимости помещать шаблонный код во все файлы. (Хотя некоторые комментарии были действительно полезны, спасибо @ncoghlan и @XiongChiamiov)
Надеюсь, что это поможет кому-то, кто борется с проблемой относительного импорта, потому что проходить PEP на самом деле не весело.
main.py
setup.py
app/ ->
__init__.py
package_a/ ->
__init__.py
module_a.py
package_b/ ->
__init__.py
module_b.py
- Ты бежишь
python main.py
, main.py
делает:import app.package_a.module_a
module_a.py
делаетimport app.package_b.module_b
В качестве альтернативы 2 или 3 можно использовать: from app.package_a import module_a
Это будет работать до тех пор, пока у вас есть app
в вашей питонпати. main.py
может быть где угодно
Так ты пишешь setup.py
скопировать (установить) весь пакет приложения и подпакеты в папки python целевой системы, и main.py
нацеливаться на папки скриптов системы.
"Гвидо рассматривает выполнение сценариев внутри пакета как анти-шаблон" (отклонено PEP-3122)
Я потратил так много времени, пытаясь найти решение, читая соответствующие посты здесь, на Stack Overflow и говоря себе: "Должен быть лучший способ!". Похоже, нет.
Это решено на 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')
Объяснение 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
, чего ты хочешь. Правильно??
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
Я использую этот фрагмент для импорта модулей из путей, надеюсь, это поможет
Это, к сожалению, взлом 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, но это не работает, если у вас уже есть определенный модуль (я нахожу это очень странным поведением)
Позвольте мне поставить это здесь для моей собственной справки. Я знаю, что это не очень хороший код Python, но мне нужен был скрипт для проекта, над которым я работал, и я хотел поместить скрипт в scripts
каталог.
import os.path
import sys
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
Как говорит @EvgeniSergeev в комментариях к ОП, вы можете импортировать код из .py
файл в произвольном месте с:
import imp
foo = imp.load_source('module.name', '/path/to/file.py')
foo.MyClass()
Это взято из этого SO ответа.
Посмотрите на http://docs.python.org/whatsnew/2.5.html. Вы могли бы сделать
from .mod1 import stuff
Вдобавок к тому, что сказал Джон Б., похоже, что __package__
переменная должна помочь, а не менять __main__
которые могут испортить другие вещи. Но, насколько я мог проверить, он работает не совсем так, как должен.
У меня та же проблема, и ни PEP 328, ни 366 не решают ее полностью, так как оба к концу дня нуждаются в том, чтобы глава пакета была включена в sys.path
насколько я мог понять.
Я должен также упомянуть, что я не нашел, как отформатировать строку, которая должна идти в эти переменные. Это "package_head.subfolder.module_name"
или что?
Из документа Python,
В Python 2.5 вы можете переключить поведение импорта на абсолютный импорт, используя
from __future__ import absolute_import
директивы. Такое поведение абсолютного импорта станет будущим по умолчанию в будущей версии (возможно, Python 2.7). Как только абсолютный импорт по умолчанию,import string
всегда найдет версию стандартной библиотеки. Предполагается, что пользователи должны начать использовать абсолютный импорт в максимально возможной степени, поэтому желательно начать писатьfrom pkg import string
в вашем коде
Я обнаружил, что проще установить переменную окружения "PYTHONPATH" в верхнюю папку:
bash$ export PYTHONPATH=/PATH/TO/APP
затем:
import sub1.func1
#...more import
Конечно, PYTHONPATH "глобальный", но это не доставило мне проблем.
Вы должны добавить путь к модулю в PYTHONPATH
:
export PYTHONPATH="${PYTHONPATH}:/path/to/your/module/"
Хакерский способ сделать это — добавить текущий каталог к PATH во время выполнения следующим образом:
import pathlib
import sys
sys.path.append(pathlib.Path(__file__).parent.resolve())
import file_to_import # the actual intended import
В отличие от другого решения этого вопроса, здесь используется
pathlib
вместо
os.path
.
Этот метод запрашивает и автоматически заполняет путь:
import os
import inspect
currentdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
parentdir = os.path.dirname(currentdir)
os.sys.path.insert(1, parentdir)
# print("currentdir = ", currentdir)
# print("parentdir=", parentdir)
Аналогично ответу Романа Арсеньева, но если вы хотите вернуться к двум каталогам:
Две директории назад:
app/
__init__.py
sub1/
__init__.py
mod1.py ------*
subsub1/ |
__init__.py mod1 and mod4
mod4.py ------|
sub2/ |
__init__.py into
mod2.py |
subsub2/ mod3
__init__.py |
mod3.py <-----*
Импортируйте мод1 в мод3:
import sys
sys.path.insert(1, '../../')
from sub1 import mod1
from sub1.subsub1 import mod
Какая дискуссия!
Относительный новичок в python (но многолетний опыт программирования и неприязнь к perl). Относительный непрофессионал, когда дело доходит до темного искусства настройки Apache, но я знаю, что мне (думаю, мне) нужно, чтобы мои маленькие экспериментальные проекты работали дома.
Вот мое резюме того, что situ кажется.
Если я использую подход -m 'module', мне нужно:-
- расставить все точки вместе;
- запустить его из родительской папки;
- потерять '.py';
- создать пустой (!)
__init__.py
файл в каждой подпапке.
Как это работает в среде cgi, где у меня есть псевдоним каталога сценариев, и я хочу запускать сценарий напрямую как /dirAlias/cgi_script.py??
Почему изменение sys.path является взломом? На странице документов python говорится: «Программа может свободно изменять этот список для своих целей». Если это работает, то это работает, верно? Счетчикам бобов в Accounts все равно, как это работает.
Я просто хочу подняться на один уровень вверх и спуститься в каталог «модули»: -
.../py
/cgi
/build
/modules
поэтому мои «модули» могут быть импортированы либо из мира cgi, либо из мира серверов.
я пробовал-m
/modules, но я думаю, что предпочитаю следующее (и меня не смущает, как запустить его в cgi-space):-
Создавать
XX_pathsetup.py
в каталоге /path/to/python/Lib (или любом другом каталоге в списке sys.path по умолчанию). «XX» — это некоторый идентификатор, который объявляет о намерении установить мой путь в соответствии с правилами в файле.В любом скрипте, который хочет иметь возможность импортировать из каталога «modules» в приведенной выше конфигурации каталога, просто
import XX_pathsetup.py
.
А вот мой очень простой XX_pathsetup.py:
import sys, os
pypath = sys.path[0].rsplit(os.sep,1)[0]
sys.path.insert( 0, pypath+os.sep+'modules' )
Не "хак", ИМХО. 1 небольшой файл для размещения в каталоге python «Lib», один оператор импорта, который объявляет о намерении изменить порядок поиска пути.
Предположим, вы работаете на верхнем уровне, а затем в mod1
использовать:
import sub2.mod2
вместо
from ..sub2 import mod2
Я думаю, что вы должны спросить себя:
- Почему я должен это сделать?
- Хорошо ли сделано разделение пакетов?
Я не знаю контекста, почему вы хотите сделать это таким образом. Но для меня более чистый дизайн будет иметь следующую структуру пакетов:
app/
__init__.py
sub1/
__init__.py
mod1.py
sub12/
__init__.py
mod2.py
Тогда вам нужно только сделать:
from sub12 import mod2