Окончательный ответ на относительный импорт Python
Я знаю, что есть много вопросов об одних и тех же проблемах импорта в Python, но, похоже, никому не удалось привести наглядный пример правильного использования.
Допустим, у нас есть пакет mypackage
с двумя модулями foo
а также bar
, внутри foo
мы должны иметь возможность доступа bar
,
Потому что мы все еще развиваем его, mypackage
не в sys.path
,
Мы хотим иметь возможность:
- Импортировать
mypackage.foo
- бежать
foo.py
в качестве сценария и выполнить пример использования или тесты из__main__
раздел. - использовать Python 2.5
Как мы должны сделать импорт в foo.py, чтобы быть уверенным, что он будет работать во всех этих случаях.
# mypackage/__init__.py
...
# mypackage/foo/__init__.py
...
# mypackage/bar.py
def doBar()
print("doBar")
# mypackage/foo/foo.py
import bar # fails with module not found
import .bar #fails due to ValueError: Attempted relative import in non-package
def doFoo():
print(doBar())
if __name__ == '__main__':
doFoo()
2 ответа
Посмотрите на следующую информацию из PEP 328:
Относительный импорт использует модуль
__name__
атрибут для определения позиции этого модуля в иерархии пакетов. Если имя модуля не содержит никакой информации о пакете (например, оно установлено на'__main__'
) затем относительный импорт разрешается, как если бы модуль был модулем верхнего уровня, независимо от того, где этот модуль фактически расположен в файловой системе.
Когда ты бежишь foo.py
как скрипт, этот модуль __name__
является '__main__'
, так что вы не можете сделать относительный импорт. Это было бы правдой, даже если mypackage
Был на sys.path
, По сути, вы можете делать относительный импорт из модуля, только если этот модуль был импортирован.
Вот несколько вариантов решения этой проблемы:
1) В foo.py
, проверить, если __name__ == '__main__'
и условно добавить mypackage
в sys.path
:
if __name__ == '__main__':
import os, sys
# get an absolute path to the directory that contains mypackage
foo_dir = os.path.dirname(os.path.join(os.getcwd(), __file__))
sys.path.append(os.path.normpath(os.path.join(foo_dir, '..', '..')))
from mypackage import bar
else:
from .. import bar
2) Всегда импортировать bar
с помощью from mypackage import bar
и выполнить foo.py
таким образом, что mypackage
виден автоматически:
$ cd <path containing mypackage>
$ python -m mypackage.foo.foo
Мое решение выглядит немного чище и может идти вверх, со всеми другими импортами:
try:
from foo import FooClass
except ModuleNotFoundError:
from .foo import FooClass