Относительный импорт в Python 3

Я хочу импортировать функцию из другого файла в том же каталоге.

Иногда это работает для меня с from .mymodule import myfunction но иногда я получаю:

 SystemError: Parent module '' not loaded, cannot perform relative import 

Иногда он работает с from mymodule import myfunction , но иногда я также получаю:

 SystemError: Parent module '' not loaded, cannot perform relative import 

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

Может ли кто-нибудь объяснить мне, что является логикой всего этого?

  • Каковы хорошие эмпирические правила для импорта Python?
  • импортировать внутри потока Python
  • ImportError: нет модуля с именем ***** в python
  • При попытке импортировать модуль с тем же именем, что и встроенный модуль, возникает ошибка импорта
  • как использовать из __future__ import print_function
  • Импортировать urllib или urllib 2 в Python 2.7 с помощью ImportError: невозможно импортировать имя iskeyword
  • Как импортировать класс Python, который находится в каталоге выше?
  • как «реимпортировать» модуль на python, после чего код будет изменен после импорта
  • 4 Solutions collect form web for “Относительный импорт в Python 3”

    к сожалению, этот модуль должен находиться внутри пакета, и иногда он также должен исполняться как скрипт. Любая идея, как я мог бы это достичь?

    Это довольно распространенный способ иметь такой макет …

     main.py mypackage/ __init__.py mymodule.py myothermodule.py 

    … с mymodule.py вот так …

     #!/usr/bin/env python3 # Exported function def as_int(a): return int(a) # Test function for module def _test(): assert as_int('1') == 1 if __name__ == '__main__': _test() 

    myothermodule.py нравится …

     #!/usr/bin/env python3 from .mymodule import as_int # Exported function def add(a, b): return as_int(a) + as_int(b) # Test function for module def _test(): assert add('1', '1') == 2 if __name__ == '__main__': _test() 

    … и main.py вот так …

     #!/usr/bin/env python3 from mypackage.myothermodule import add def main(): print(add('1', '1')) if __name__ == '__main__': main() 

    … который отлично работает при запуске main.py или mypackage/mymodule.py , но не работает с mypackage/myothermodule.py из-за относительного импорта …

     from .mymodule import as_int 

    То, как вы должны запускать его, – это …

     python3 -m mypackage.myothermodule 

    … но он несколько подробный и не очень хорошо сочетается с строкой shebang, например, #!/usr/bin/env python3 .

    Самое простое исправление для этого случая, если имя mymodule уникально глобально, было бы избежать использования относительного импорта и просто использовать …

     from mymodule import as_int 

    … хотя, если это не уникально, или ваша структура пакета более сложна, вам нужно будет включить каталог, содержащий каталог вашего пакета в PYTHONPATH , и сделать это вот так …

     from mypackage.mymodule import as_int 

    … или если вы хотите, чтобы он работал «из коробки», вы можете frob PYTHONPATH в коде сначала с этим …

     import sys import os PACKAGE_PARENT = '..' SCRIPT_DIR = os.path.dirname(os.path.realpath(os.path.join(os.getcwd(), os.path.expanduser(__file__)))) sys.path.append(os.path.normpath(os.path.join(SCRIPT_DIR, PACKAGE_PARENT))) from mypackage.mymodule import as_int 

    Это своего рода боль, но есть ключ к тому, почему в письме, написанном неким Гвидо ван Россумом …

    Я нахожусь на этом и на любых других предлагаемых твидлингах машины __main__ . Единственный случай использования, похоже, заключается в запуске скриптов, которые, случается, живут внутри каталога модуля, который я всегда рассматривал как антипаттерн. Чтобы заставить меня передумать, нужно убедить меня, что это не так.

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

    объяснение

    В какой-то момент PEP 338 столкнулся с PEP 328 :

    … относительный импорт зависит от __name__, чтобы определить позицию текущего модуля в иерархии пакетов. В основном модуле значение __name__ всегда «__main__» , поэтому явные относительные импорты всегда будут терпеть неудачу (поскольку они работают только для модуля внутри пакета)

    и для решения проблемы PEP 366 представил переменную верхнего уровня __package__ :

    Добавляя новый атрибут уровня модуля, этот PEP позволяет относительным импортам работать автоматически, если модуль выполняется с использованием -m- переключателя. Небольшое количество шаблонов в самом модуле позволит относительным импортам работать, когда файл выполняется по имени. […] Когда он [атрибут] присутствует, относительный импорт будет основываться на этом атрибуте, а не на атрибуте __name__ модуля. […] Когда основной модуль указан его именем файла, атрибут __package__ будет установлен на None . […] Когда система импорта встречает явный относительный импорт в модуле без установленного __package__ (или с ним установлен None), он будет вычислять и сохранять правильное значение ( __name __. Rpartition ('.') [0] для нормальные модули и __name__ для модулей инициализации пакетов)

    (акцент мой)

    Если __name__ является '__main__' , __name__.rpartition('.')[0] возвращает пустую строку. Вот почему в описании ошибки есть пустой строковый литерал:

     SystemError: Parent module '' not loaded, cannot perform relative import 

    Соответствующая часть функции PyImport_ImportModuleLevelObject CPython:

     if (PyDict_GetItem(interp->modules, package) == NULL) { PyErr_Format(PyExc_SystemError, "Parent module %R not loaded, cannot perform relative " "import", package); goto error; } 

    CPython вызывает это исключение, если он не смог найти package (имя пакета) в interp->modules (доступные как sys.modules ). Поскольку sys.modules – это «словарь, который сопоставляет имена модулей уже загруженным модулям» , теперь становится ясно, что родительский модуль должен быть явно импортирован до импорта, прежде чем выполнять относительный импорт .

    Примечание . Патч из вопроса 18018 добавил еще один блок if , который будет выполнен до кода выше:

     if (PyUnicode_CompareWithASCIIString(package, "") == 0) { PyErr_SetString(PyExc_ImportError, "attempted relative import with no known parent package"); goto error; } /* else if (PyDict_GetItem(interp->modules, package) == NULL) { ... */ 

    Если package (такой же, как указано выше) является пустой строкой, сообщение об ошибке будет

     ImportError: attempted relative import with no known parent package 

    Однако вы увидите это только в Python 3.6 или новее.

    Решение №1: Запустите свой скрипт с помощью -m

    Рассмотрим каталог (который представляет собой пакет Python):

     . ├── package │  ├── __init__.py │  ├── module.py │  └── standalone.py 

    Все файлы в пакете начинаются с тех же двух строк кода:

     from pathlib import Path print('Running' if __name__ == '__main__' else 'Importing', Path(__file__).resolve()) 

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

    __init__.py и module.py содержат только те две строки (то есть они фактически пусты).

    standalone.py дополнительно пытается импортировать module.py через относительный импорт:

     from . import module # explicit relative import 

    Мы хорошо знаем, что /path/to/python/interpreter package/standalone.py завершится с ошибкой. Однако мы можем запустить модуль с параметром командной строки -m который будет «искать sys.path для именованного модуля и выполнять его содержимое как модуль __main__ » :

     vaultah@base:~$ python3 -i -m package.standalone Importing /home/vaultah/package/__init__.py Running /home/vaultah/package/standalone.py Importing /home/vaultah/package/module.py >>> __file__ '/home/vaultah/package/standalone.py' >>> __package__ 'package' >>> # The __package__ has been correctly set and module.py has been imported. ... # What's inside sys.modules? ... import sys >>> sys.modules['__main__'] <module 'package.standalone' from '/home/vaultah/package/standalone.py'> >>> sys.modules['package.module'] <module 'package.module' from '/home/vaultah/package/module.py'> >>> sys.modules['package'] <module 'package' from '/home/vaultah/package/__init__.py'> 

    -m делает все импортирующее вещество для вас и автоматически устанавливает __package__ , но вы можете сделать это сами в

    Решение №2: Установите __package__ вручную

    Пожалуйста, рассматривайте его как доказательство концепции, а не фактическое решение. Он не подходит для использования в реальном коде.

    К сожалению, установить только __package__ недостаточно. Вам потребуется импортировать по крайней мере N предыдущих пакетов в иерархию модулей, где N – количество родительских каталогов (относительно каталога сценария), на которые будет произведен поиск импортируемого модуля.

    Таким образом,

    1. Добавьте родительский каталог N-го предшественника текущего модуля в sys.path

    2. Удалить каталог текущего файла из sys.path

    3. Импортируйте родительский модуль текущего модуля, используя его полное имя

    4. Установите __package__ на полное имя от 2

    5. Выполните относительный импорт

    Я возьму файлы из решения №1 и добавлю еще несколько подпакетов:

     package ├── __init__.py ├── module.py └── subpackage ├── __init__.py └── subsubpackage ├── __init__.py └── standalone.py 

    На этот раз standalone.py импортирует module.py из пакета пакетов, используя следующий относительный импорт

     from ... import module # N = 3 

    Нам нужно будет предшествовать этой строке с кодом шаблона, чтобы он работал.

     import sys from pathlib import Path if __name__ == '__main__' and __package__ is None: file = Path(__file__).resolve() parent, top = file.parent, file.parents[3] sys.path.append(str(top)) try: sys.path.remove(str(parent)) except ValueError: # Already removed pass import package.subpackage.subsubpackage __package__ = 'package.subpackage.subsubpackage' from ... import module # N = 3 

    Это позволяет нам выполнить standalone.py по имени файла:

     vaultah@base:~$ python3 package/subpackage/subsubpackage/standalone.py Running /home/vaultah/package/subpackage/subsubpackage/standalone.py Importing /home/vaultah/package/__init__.py Importing /home/vaultah/package/subpackage/__init__.py Importing /home/vaultah/package/subpackage/subsubpackage/__init__.py Importing /home/vaultah/package/module.py 

    Более общее решение, заключенное в функцию, можно найти здесь . Пример использования:

     if __name__ == '__main__' and __package__ is None: import_parents(level=3) # N = 3 from ... import module from ...module.submodule import thing 

    Решение № 3: Используйте абсолютный импорт и setuptools

    Шаги –

    1. Заменить явный относительный импорт с эквивалентным абсолютным импортом

    2. Установите package чтобы сделать его импортируемым.

    Например, структура каталогов может быть следующей:

     . ├── project │  ├── package │  │  ├── __init__.py │  │  ├── module.py │  │  └── standalone.py │  └── setup.py 

    где setup.py

     from setuptools import setup, find_packages setup( name = 'your_package_name', packages = find_packages(), ) 

    Остальные файлы были заимствованы из решения №1 .

    Установка позволит вам импортировать пакет независимо от вашего рабочего каталога (при условии, что проблем с именованием не будет).

    Мы можем изменить standalone.py для использования этого преимущества (шаг 1):

     from package import module # absolute import 

    Измените рабочий каталог на project и запустите /path/to/python/interpreter setup.py install --user ( --user устанавливает пакет в каталог вашего сайта ) (шаг 2):

     vaultah@base:~$ cd project vaultah@base:~/project$ python3 setup.py install --user 

    Давайте проверим, что теперь можно запустить standalone.py как скрипт:

     vaultah@base:~/project$ python3 -i package/standalone.py Running /home/vaultah/project/package/standalone.py Importing /home/vaultah/.local/lib/python3.6/site-packages/your_package_name-0.0.0-py3.6.egg/package/__init__.py Importing /home/vaultah/.local/lib/python3.6/site-packages/your_package_name-0.0.0-py3.6.egg/package/module.py >>> module <module 'package.module' from '/home/vaultah/.local/lib/python3.6/site-packages/your_package_name-0.0.0-py3.6.egg/package/module.py'> >>> import sys >>> sys.modules['package'] <module 'package' from '/home/vaultah/.local/lib/python3.6/site-packages/your_package_name-0.0.0-py3.6.egg/package/__init__.py'> >>> sys.modules['package.module'] <module 'package.module' from '/home/vaultah/.local/lib/python3.6/site-packages/your_package_name-0.0.0-py3.6.egg/package/module.py'> 

    Примечание . Если вы решите пойти по этому маршруту, вам будет лучше использовать виртуальные среды для установки пакетов по отдельности.

    Решение № 4: Используйте абсолютный импорт и некоторый шаблонный код

    Честно говоря, установка не нужна – вы могли бы добавить в свой скрипт код шаблона, чтобы сделать работу с абсолютным импортом.

    Я собираюсь брать файлы из решения №1 и менять standalone.py :

    1. Добавьте родительский каталог пакета в sys.path прежде чем пытаться импортировать что-либо из пакета, используя абсолютный импорт:

       import sys from pathlib import Path # if you haven't already done so file = Path(__file__).resolve() parent, root = file.parent, file.parents[1] sys.path.append(str(root)) # Additionally remove the current file's directory from sys.path try: sys.path.remove(str(parent)) except ValueError: # Already removed pass 
    2. Заменить относительный импорт абсолютным импортом:

       from package import module # absolute import 

    standalone.py работает без проблем:

     vaultah@base:~$ python3 -i package/standalone.py Running /home/vaultah/package/standalone.py Importing /home/vaultah/package/__init__.py Importing /home/vaultah/package/module.py >>> module <module 'package.module' from '/home/vaultah/package/module.py'> >>> import sys >>> sys.modules['package'] <module 'package' from '/home/vaultah/package/__init__.py'> >>> sys.modules['package.module'] <module 'package.module' from '/home/vaultah/package/module.py'> 

    Я чувствую, что должен предупредить вас: постарайтесь не делать этого, особенно если ваш проект имеет сложную структуру.


    В качестве дополнительной заметки PEP 8 рекомендует использовать абсолютный импорт, но заявляет, что в некоторых сценариях допустимый относительный импорт является приемлемым:

    Абсолютный импорт рекомендуется, так как они обычно более читабельны и, как правило, лучше себя ведут (или, по крайней мере, дают лучшие сообщения об ошибках). […] Однако явный относительный импорт является приемлемой альтернативой абсолютному импорту, особенно при работе с сложными макетами пакетов, где использование абсолютного импорта будет излишне подробным.

    Я столкнулся с этой проблемой. Работа Hack работает с помощью блока try / except, подобного этому:

     #!/usr/bin/env python3 #myothermodule if __name__ == '__main__': from mymodule import as_int else: from .mymodule import as_int # Exported function def add(a, b): return as_int(a) + as_int(b) # Test function for module def _test(): assert add('1', '1') == 2 if __name__ == '__main__': _test() 

    если оба пакета находятся в вашем пути импорта (sys.path), а требуемый модуль / класс находится в примере / example.py, то для доступа к классу без относительного импорта выполните:

     from example.example import fkt 
    Interesting Posts

    Многопроцессор Python с помощью PyCUDA

    Получение количества комментариев в списке видеороликов youtube

    Сохраните изображение в файл изображения вместо его отображения с помощью Matplotlib

    Определить параметр запроса в app.yaml в приложении Google Appengine

    matplotlib: рисование линий между точками, игнорирующими отсутствующие данные

    Как подключить удаленный отладчик к процессу Python?

    Concurrent.futures vs Multiprocessing в Python 3

    2d hsv цветовое пространство в matplotlib

    Воспроизвести видеофайл с VLC, затем выйти из VLC

    Python Как просто перенаправить вывод печати в файл TXT с новой строкой, созданной для каждого перенаправления

    Применение прописных букв к столбцу в кадре данных pandas

    Возможно ли записать объект фрейма python, возвращенный sys._getframe () из кода python, запущенного в интерпретаторе?

    Ошибка установки python blpapi

    Есть ли лучший язык компоновки, чем HTML для печати?

    Где символы амперсанда и вертикальной строки, используемые в Python?

    Python - лучший язык программирования в мире.