Относительный импорт в миллиардный раз
Я был здесь:
- PEP 328 – Импорт: многострочный и абсолютный / относительный
- Модули, пакеты
- Пакеты Python: относительный импорт
- Пример кода относительного импорта Python не работает
- Относительный импорт в Python 2.5
- Относительный импорт в Python
- Python: отключение относительного импорта
и множество URL-адресов, которые я не копировал, некоторые на SO, некоторые на других сайтах, когда я думал, что быстро найду решение.
Вечно повторяющийся вопрос таков: как мне решить это сообщение "Попытка относительного импорта без пакета"?
ImportError: попытка относительного импорта без известного родительского пакета
Я создал точную копию пакета на pep-0328:
package/
__init__.py
subpackage1/
__init__.py
moduleX.py
moduleY.py
subpackage2/
__init__.py
moduleZ.py
moduleA.py
Импорт был выполнен из консоли.
Я создал функции с именами spam и eggs в соответствующих модулях. Естественно, это не сработало. Ответ, по-видимому, находится в 4-м URL, который я перечислил, но для меня это все выпускники. На одном из URL-адресов, которые я посетил, был этот ответ:
Относительный импорт использует атрибут имени модуля для определения положения этого модуля в иерархии пакетов. Если имя модуля не содержит никакой информации о пакете (например, оно установлено в 'main'), то относительный импорт разрешается так, как если бы модуль был модулем верхнего уровня, независимо от того, где модуль фактически расположен в файловой системе.
Приведенный выше ответ выглядит многообещающим, но для меня это все иероглифы. Как мне заставить Python не возвращать мне "Попытка относительного импорта без пакета"? Предположительно, у него есть ответ, который включает -m
.
Почему Python выдает это сообщение об ошибке? Что означает "непакетный"? Почему и как вы определяете "пакет"?
Переведено автоматически
Ответ 1
Сценарий и Модуль
Вот объяснение. Короткая версия заключается в том, что существует большая разница между прямым запуском файла Python и импортом этого файла откуда-то еще. Простое знание того, в каком каталоге находится файл, не определяет, в каком пакете, по мнению Python, он находится. Это зависит, кроме того, от того, как вы загружаете файл в Python (запуском или импортом).
Есть два способа загрузить файл Python: как скрипт верхнего уровня или как модуль. Файл загружается как скрипт верхнего уровня, если вы выполняете его напрямую, например, набрав python myfile.py
в командной строке. Он загружается как модуль, когда оператор import
встречается внутри какого-либо другого файла. Одновременно может быть только один скрипт верхнего уровня; скрипт верхнего уровня - это файл Python, который вы запустили для начала работы.
Именование
При загрузке файла ему присваивается имя (которое хранится в его __name__
атрибуте). Если он был загружен как скрипт верхнего уровня, его имя - __main__
. Если он был загружен как модуль, его именем является filename, перед которым стоят названия любых пакетов / подпакетов, частью которых он является, разделенные точками.
Так, например, в вашем примере:
package/
__init__.py
subpackage1/
__init__.py
moduleX.py
moduleA.py
если вы импортировали moduleX
(примечание: импортировали, а не выполняли напрямую), его имя будет package.subpackage1.moduleX
. Если вы импортировали moduleA
, его имя будет package.moduleA
. Однако, если вы напрямую запустите moduleX
из командной строки, его имя вместо этого будет __main__
, а если вы напрямую запустите moduleA
из командной строки, его имя будет __main__
. Когда модуль запускается как скрипт верхнего уровня, он теряет свое обычное имя и вместо него используется __main__
.
Доступ к модулю НЕ через содержащий его пакет
Есть дополнительная загвоздка: имя модуля зависит от того, было ли оно импортировано "напрямую" из каталога, в котором оно находится, или импортировано через пакет. Это имеет значение, только если вы запускаете Python в каталоге и пытаетесь импортировать файл в этот же каталог (или его подкаталог). Например, если вы запустите интерпретатор Python в каталоге, package/subpackage1
а затем выполните import moduleX
, имя moduleX
будет просто moduleX
, а не package.subpackage1.moduleX
. Это связано с тем, что Python добавляет текущий каталог в свой путь поиска при интерактивном вводе интерпретатора; если он найдет импортируемый модуль в текущем каталоге, он не будет знать, что этот каталог является частью пакета, и информация о пакете не станет частью имени модуля.
Особый случай - это когда вы запускаете интерпретатор в интерактивном режиме (например, просто вводите python
и начинаете вводить код Python "на лету"). В этом случае имя этого интерактивного сеанса - __main__
.
Теперь самое главное для вашего сообщения об ошибке: если в имени модуля нет точек, он не считается частью пакета. Не имеет значения, где на самом деле находится файл на диске. Важно только его имя, а его имя зависит от того, как вы его загрузили.
Теперь взгляните на цитату, которую вы включили в свой вопрос:
Относительный импорт использует атрибут имени модуля для определения положения этого модуля в иерархии пакетов. Если имя модуля не содержит никакой информации о пакете (например, оно установлено в 'main'), то относительный импорт разрешается так, как если бы модуль был модулем верхнего уровня, независимо от того, где модуль фактически расположен в файловой системе.
Относительный импорт...
Относительный импорт использует имя модуля, чтобы определить, где он находится в пакете. Когда вы используете относительный импорт, подобный from .. import foo
, точки указывают на повышение некоторого количества уровней в иерархии пакетов. Например, если имя вашего текущего модуля package.subpackage1.moduleX
, то ..moduleA
будет означать package.moduleA
. Чтобы a from .. import
работал, в имени модуля должно быть как минимум столько точек, сколько в import
инструкции.
... относительны только в пакете
Однако, если имя вашего модуля __main__
, он не считается находящимся в пакете. В его имени нет точек, и поэтому вы не можете использовать from .. import
инструкции внутри него. Если вы попытаетесь это сделать, вы получите ошибку "относительный импорт не в пакете".
Скрипты не могут импортировать относительный
Что вы, вероятно, сделали, так это попытались запустить moduleX
или что-то подобное из командной строки. Когда вы это делали, его имени было присвоено значение __main__
, что означает, что относительный импорт внутри него завершится неудачей, потому что его имя не показывает, что он находится в пакете. Обратите внимание, что это также произойдет, если вы запустите Python из того же каталога, где находится модуль, а затем попытаетесь импортировать этот модуль, потому что, как описано выше, Python обнаружит модуль в текущем каталоге "слишком рано", не осознавая, что он является частью пакета.
Также помните, что при запуске интерактивного интерпретатора "именем" этого интерактивного сеанса всегда является __main__
. Таким образом, вы не можете выполнять относительный импорт непосредственно из интерактивного сеанса. Относительный импорт предназначен только для использования в файлах модулей.
Два решения:
Если вы действительно хотите запустить
moduleX
напрямую, но все еще хотите, чтобы это считалось частью пакета, вы можете сделатьpython -m package.subpackage1.moduleX
.-m
сообщает Python загрузить его как модуль, а не как скрипт верхнего уровня.Или, возможно, вы на самом деле не хотите запускать
moduleX
, вы просто хотите запустить какой-то другой скрипт, скажем,myfile.py
, который использует функции внутриmoduleX
. Если это так, поместитеmyfile.py
куда–нибудь еще - не внутриpackage
каталога – и запустите его. Если внутриmyfile.py
вы делаете что-то вродеfrom package.moduleA import spam
, это будет работать нормально.
Примечания
Для любого из этих решений каталог пакета (
package
в вашем примере) должен быть доступен по пути поиска модуля Python (sys.path
). Если это не так, вы вообще не сможете надежно использовать что-либо в пакете.Начиная с Python 2.6, "имя" модуля для целей разрешения пакетов определяется не только его
__name__
атрибутами, но и__package__
атрибутом. Вот почему я избегаю использования явного символа__name__
для обозначения "имени" модуля. Начиная с Python 2.6, "имя" модуля фактически__package__ + '.' + __name__
, или просто__name__
if__package__
isNone
.)
Ответ 2
Это действительно проблема в python. Причина путаницы в том, что люди ошибочно принимают относительный импорт за относительный путь, который таковым не является.
Например, когда вы пишете в faa.py:
from .. import foo
Это имеет значение, только если faa.py был идентифицирован и загружен python во время выполнения как часть пакета. В этом случае имя модуля
для faa.py было бы, например, some_packagename.faa. Если файл был загружен только потому, что он находится в текущем каталоге, то при запуске python его имя не будет ссылаться ни на один пакет, и в конечном итоге относительный импорт завершится неудачей.
Простое решение для ссылки на модули в текущем каталоге - использовать это:
if __package__ is None or __package__ == '':
# uses current directory visibility
import foo
else:
# uses current package visibility
from . import foo
Ответ 3
Слишком много длинных ответов на иностранном языке. Итак, я постараюсь сделать его коротким.
Если вы напишете from . import module
вопреки тому, что вы думаете, module
импорт будет произведен не из текущего каталога, а с верхнего уровня вашего пакета! Если вы запускаете файл .py как скрипт, он просто не знает, где находится верхний уровень, и поэтому отказывается работать.
Если вы запустите его вот так py -m package.module
из каталога выше package
, то Python знает, где находится верхний уровень. Это очень похоже на Java: java -cp bin_directory package.class
Ответ 4
Итак, после придирок к этому наряду со многими другими, я наткнулся на заметку, опубликованную Дорианом Б. в этой статье, которая решила конкретную проблему, с которой я столкнулся, когда я разрабатывал модули и классы для использования с веб-сервисом, но я также хочу иметь возможность тестировать их во время написания кода, используя возможности отладчика в PyCharm. Чтобы запускать тесты в автономном классе, я бы включил следующее в конец моего файла класса:
if __name__ == '__main__':
# run test code here...
но если бы я захотел импортировать другие классы или модули в ту же папку, мне тогда пришлось бы изменить все мои инструкции импорта с относительных обозначений на локальные ссылки (т. Е. Убрать точку (.)) Но после прочтения предложения Дориана я попробовал его "однострочный" вариант, и это сработало! Теперь я могу тестировать в PyCharm и оставлять свой тестовый код на месте, когда я использую класс в другом тестируемом классе или когда я использую его в своем веб-сервисе!
# import any site-lib modules first, then...
import sys
parent_module = sys.modules['.'.join(__name__.split('.')[:-1]) or '__main__']
if __name__ == '__main__' or parent_module.__name__ == '__main__':
from codex import Codex # these are in same folder as module under test!
from dblogger import DbLogger
else:
from .codex import Codex
from .dblogger import DbLogger
Оператор if проверяет, запускаем ли мы этот модуль как основной или он используется в другом модуле, который тестируется как основной. Возможно, это очевидно, но я предлагаю это примечание здесь на случай, если кто-то еще, разочарованный проблемами относительного импорта, описанными выше, сможет им воспользоваться.