Окончательный ответ на относительный импорт 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
Другие вопросы по тегам