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

Я знаю, что есть много вопросов о тех же проблемах с импортом в Python, но кажется, что никто не смог обеспечить ясный пример правильного использования.

Скажем, что у нас есть пакет mypackage с двумя модулями foo и bar. Внутри foo нам нужно иметь доступ к bar.

Поскольку мы все еще его разрабатываем, mypackage не находится в sys.path.

Мы хотим иметь возможность:

  • import mypackage.foo
  • запустите foo.py как script и выполните пример использования или тесты из раздела __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()

Ответ 1

Взгляните на следующую информацию от PEP 328:

Относительный импорт использует атрибут __name__ для определения позиции модуля в иерархии пакетов. Если имя модуля не содержит никакой информации о пакете (например, оно установлено на '__main__'), то относительный импорт разрешается, как если бы модуль был модулем верхнего уровня, независимо от того, где модуль фактически находится в файловой системе.

Когда вы запускаете foo.py как script, этот модуль __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

Ответ 2

Мое решение выглядит немного чище и может идти вверху, со всеми другими импортами:

try:
   from foo import FooClass
except ModuleNotFoundError:
   from .foo import FooClass