Окончательный ответ на относительный импорт Python - PullRequest
14 голосов
/ 28 ноября 2011

Я знаю, что есть много вопросов об одних и тех же проблемах импорта в 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 ]

28 голосов
/ 28 ноября 2011

Посмотрите на следующую информацию от 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
5 голосов
/ 03 августа 2017

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

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