Как я могу использовать относительный импорт в Python3 с блоком if __name __ = '__ main__'? - PullRequest
0 голосов
/ 02 февраля 2019

Я делаю пакет, и модули в этом пакете имеют код внутри if __name__=='__main__': блоков для тестирования.Но мои попытки использовать относительный импорт в этих модулях вызывают ошибки.

Я прочитал эту ветку и миллиард других: Относительный импорт в миллиардный раз

Прежде чем пометить это как дубликат, если то, что я хочу сделать, невозможно в Python3, тогда мой вопрос: почему он работал в Python2 и что послужило причиной решения сделать такую ​​проблему в Python3?


Это мой пример проекта Python:

mypackage
- module1.py
- module2.py
- __init__.py

__init__.py и module2.py пусты

module1.py содержит:

import module2

# module1 contents

if __name__=="__main__":
    # Some test cases for the contents of this module
    pass

Это прекрасно работает в Python2.Я могу импортировать module1 из других проектов в любом месте моего компьютера, и я также могу запускать module1 напрямую и запускать код в блоке if.

Однако эта структура не работаетв Python3.Если я попытаюсь импортировать модуль куда-то еще, он потерпит неудачу:

>>> from mypackage import module1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\_MyFiles\Programming\Python Modules\mypackage\module1.py", line 1, in <module>
    import module2
ModuleNotFoundError: No module named 'module2'

Поэтому я попытался изменить первую строку на from . import module2, и это исправило ее, чтобы я мог успешно импортировать модуль из любого места.Но затем, когда я пытаюсь запустить module1 напрямую как скрипт, я получаю эту ошибку:

Traceback (most recent call last):
  File "C:/_MyFiles/Programming/Python Modules/mypackage/module1.py", line 1, in <module>
    from . import module2
ImportError: cannot import name 'module2' from '__main__' (C:/_MyFiles/Programming/Python Projects/pgui/mypackage/module1.py)

Мне не нужно открывать консоль и вводить python -m myfile каждый раз, когда я работаю над модулеми хочу запустить его напрямую как скрипт.

Я хочу иметь возможность работать с модулями без добавления их родительской папки в PYTHONPATH, используя относительный импорт, как в Python2

Есть ли лучший обходной путьили решение этих проблем?

Ответы [ 3 ]

0 голосов
/ 02 февраля 2019

Пакет Python - это не просто папка, в которую вы вставляете свой код, а поведение импорта зависит не только от того, в какую папку вы вставили свой код.

Когда вы запускаете файл напрямую, выне запускаем его как часть пакета.Инициализация на уровне пакета не выполняется, и Python даже не распознает существование пакета.В Python 2 наличие неявного относительного импорта означало, что пустая import module2 могла бы разрешить либо абсолютный импорт, либо неявный относительный импорт, что скрыло проблему, но структура импорта все еще не работает.В Python 3 неявный относительный импорт исчез (по уважительной причине), поэтому проблема сразу видна.

Запуск подмодуля пакета непосредственно по имени файла просто не очень хорошо работает.Сегодня я считаю, что стандартом является либо использование -m, либо использование сценария точки входа верхнего уровня, который вызывает функциональность подмодуля.

Существует своего рода способ заставить работать файл по имени в любом случае, но это много шаблонного.Разработчики PEP 366 , похоже, намеревались назначить __package__ = 'appropriate.value' для правильной работы относительного импорта, но этого на самом деле недостаточно, даже если вы исправите путь импорта.Вам также придется инициализировать родительский пакет вручную, или вы получите «SystemError: Родительский модуль« foo »не загружен, не может выполнить относительный импорт», как только вы попытаетесь запустить относительный импорт.Полный шаблон выглядит как

import os.path
import sys
if __name__ == '__main__' and __package__ is None:
    __package__ = 'mypackage'
    right_import_root = os.path.abspath(__file__)
    for i in range(__package__.count('.') + 2):
        right_import_root = os.path.dirname(right_import_root)

    # sys.path[0] is usually the right sys.path entry to replace, but this
    # may need further refinement in the presence of anything else that messes
    # with sys.path
    sys.path[0] = right_import_root
    __import__(__package__)

Это относится к таким вещам, как будущий импорт, но перед любым импортом, который зависит от вашего пакета.

Я бы обернул этот шаблон в функцию многократного использования (используяманипулирование стеком для доступа к глобальным переменным вызывающего объекта), за исключением того, что если вы попытаетесь поместить эту функцию где-то в своем проекте, вы не сможете импортировать функцию, пока не исправите свою ситуацию импорта, что необходимо для этой функции.Это может работать как устанавливаемая зависимость.

0 голосов
/ 02 февраля 2019

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

Рассмотрим следующую структуру

mydir
- project
  - __init__.py
  - module1.py
  - module2.py

Содержимое module1 и module2 выглядит следующим образом

module1.py

print("moudule1")

moudle2.py

с.import module1

print("Module 2")

if __name__ == '__main__':
    print("Executed as script")

Теперь, если я открою repl вне каталога пакета и попытаюсь сделать импорт, он будет работать

Python 3.6.7 (default, Oct 22 2018, 11:32:17) 
[GCC 8.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from package import module2
Module 1
Module 2
>>> sys.path
['', '/usr/lib/python36.zip', '/usr/lib/python3.6', '/usr/lib/python3.6/lib-dynload', '/home/rbhanot/.local/lib/python3.6/site-packages', '/usr/local/lib/python3.6/dist-packages', '/usr/lib/python3/dist-packages']

Обратите внимание на sys.path, как вы можете видетьсодержит текущий каталог, в котором я нахожусь в качестве первого элемента, что означает, что все мои операции импорта будут сначала найдены в моем текущем каталоге.

Теперь, если я зайду в каталог пакета, а затем открою repl и попробуюВыполнение одного и того же импорта. Посмотрите, что происходит*, так как я не смог найти ни одного, следовательно, импорт завершился неудачно.Но импорт module1 работает, потому что он находится в текущем каталоге.

Вне пакета я могу выполнить скрипт как

python3 -m package.module2                                                                              2 ↵
Module 1
Module 2
Executed as script

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

0 голосов
/ 02 февраля 2019

Согласно документации для модулей , для модулей __main__ необходимо использовать абсолютный импорт.

Обратите внимание, что относительный импорт основан на имени текущего модуля.,Поскольку имя основного модуля всегда " main ", модули, предназначенные для использования в качестве основного модуля приложения Python, всегда должны использовать абсолютный импорт.

Так что просто изменитестрока импорта в module1.py в:

from mypackage import module2

Все остальное остается прежним.

...