Вопрос-Ответ

Relative imports for the billionth time

Относительный импорт в миллиардный раз

Я был здесь:

и множество 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__. Таким образом, вы не можете выполнять относительный импорт непосредственно из интерактивного сеанса. Относительный импорт предназначен только для использования в файлах модулей.

Два решения:


  1. Если вы действительно хотите запустить moduleX напрямую, но все еще хотите, чтобы это считалось частью пакета, вы можете сделать python -m package.subpackage1.moduleX. -m сообщает Python загрузить его как модуль, а не как скрипт верхнего уровня.



  2. Или, возможно, вы на самом деле не хотите запускать 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__ is None.)



Ответ 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 проверяет, запускаем ли мы этот модуль как основной или он используется в другом модуле, который тестируется как основной. Возможно, это очевидно, но я предлагаю это примечание здесь на случай, если кто-то еще, разочарованный проблемами относительного импорта, описанными выше, сможет им воспользоваться.

python