Относительно импорта в 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
Я не понимаю логики здесь, и я не мог найти никакого объяснения. Это выглядит совершенно случайным.
может кто-нибудь объяснить мне, в чем логика всего этого?
8 ответов:
к сожалению, этот модуль должен быть внутри упаковки, а также иногда его нужно запускать как скрипт. Любая идея, как я мог добиться этого?
это довольно часто, чтобы иметь такое расположение...
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
это своего рода боль, но есть ключ к тому, почему в по электронной почте написано неким Гвидо ван Россумом...
я -1 на этом и на любом другом предложенном вертушки из
__main__
механизм. Единственный вариант использования, похоже, запускает сценарии, которые происходят чтобы жить внутри каталога модуля, который я всегда видел как антимодель. Чтобы заставить меня передумать, тебе придется убедить меня в этом. это не так.является ли запуск скриптов внутри пакета антипаттерном или нет, субъективно, но лично я нахожу его действительно полезным в пакете, который у меня есть, который содержит некоторые пользовательские виджеты wxPython, поэтому я могу запустить скрипт для любой из исходных файлов для отображения
wx.Frame
содержит только этот виджет для целей тестирования.
объяснение
с PEP 328
относительный импорт используйте атрибут __name_ _ модуля, чтобы определить, что позиция модуля в иерархии пакетов. Если имя модуля делает не содержит никакой информации о пакете (например, он установлен в ' _ _ main__') затем относительный импорт разрешается, как если бы модуль был верхнего уровня модуль, независимо от того, где на самом деле находится модуль в файле система.
в какой-то момент PEP 338 противоречит PEP 328:
... относительный импорт зависит от _ _ name__ для определения текущего позиция модуля в иерархии пакетов. В главном модуле значение _ _ name__ всегда ' _ _ main__', так откровенно, относительный импорт всегда будет терпеть неудачу (так как они работают только для модуля внутри пакет)
и чтобы решить эту проблему, PEP 366 введена переменная верхнего уровня
__package__
:добавляя новый атрибут уровня модуля, этот PEP позволяет относительное импорт будет работать автоматически, если модуль выполняется с помощью - m переключатель. Небольшое количество шаблона в самом модуле позволит относительный импорт для работы, когда файл выполняется по имени. [...] Когда он [ атрибут] присутствует, относительный импорт будет основан на этом атрибуте а не модуль _ _ name__. [...] Если основной модуль задан его именем файла, то __пакет__ атрибут будет установлен в нет. [...]когда система импорта сталкивается с явным относительным импортом в a модуль без __package__ set (или с установленным значением None), он будет вычислить и сохранить правильное значение ( _ _ name__.rpartition('.')[0] для обычных модулей и _ _ name__ для модулей инициализации пакета)
(выделено мной)
если
__name__
и'__main__'
,__name__.rpartition('.')[0]
возвращает пустую строку. Вот почему в описании ошибки есть пустой строковый литерал:SystemError: Parent module '' not loaded, cannot perform relative import
в соответствующей части с CPython это
PyImport_ImportModuleLevelObject
функции: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
(так же, как и выше) пустая строка, сообщение об ошибке будет, вы увидите это только в Python 3.6 или новее.ImportError: attempted relative import with no known parent package
Решение №1: запустите скрипт с помощью -m
рассмотрим каталог (который является Python пакета):
. ├── package │ ├── __init__.py │ ├── module.py │ └── standalone.py
все файлы в пакета начните с тех же 2 строк кода:
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: установите _ _ пакет _ _ вручную
пожалуйста, рассматривайте его как доказательство концепции, а не фактическое решение. Он не очень хорошо подходит для использования в реальном коде.
PEP 366 имеет обходной путь к этой проблеме, однако, это неполный, потому что установка
__package__
одного недостаточно. Вам нужно будет импортировать по крайней мере N предыдущие пакеты в иерархии модулей, где N - количество родительских каталогов (относительно каталога скрипта), в которых будет выполняться поиск импортируемого модуля.таким образом,
добавить родительский каталог Nth предшественник текущего модуля на
sys.path
удалите каталог текущего файла из
sys.path
импорт родительского модуля текущего модуля с помощью его полное имя
Set
__package__
к полному имени от 2выполните относительный импорт
я позаимствую файлы из Решение № 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
шаги -
заменить явный относительный импорт эквивалентным абсолютным импортом
установить
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 как a сценарий:
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:
добавить родительский каталог пакета до
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
заменить относительный импорт на абсолютный импорт:
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 рекомендует использовать абсолютный импорт, но заявляет, что в некоторых сценариях явный относительный импорт допустим:
абсолютный импорт рекомендуется, так как они обычно более читабельны и, как правило, лучше себя ведут (или хотя бы дают лучшая ошибка сообщения.) [...] Однако явный относительный импорт является приемлемым альтернатива абсолютному импорту, особенно при работе со сложными макеты пакетов, где использование абсолютного импорта было бы излишне многословный.
я столкнулся с этой проблемой. Обходной путь взлома импортируется через блок if / else следующим образом:
#!/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()
положите это в ваш пакет __init__.py файл:
# For relative imports to work in Python 3.6 import os, sys; sys.path.append(os.path.dirname(os.path.realpath(__file__)))
предполагая, что ваш пакет выглядит так:
├── project │ ├── package │ │ ├── __init__.py │ │ ├── module1.py │ │ └── module2.py │ └── setup.py
Теперь используйте регулярные импорт в вас пакет, как:
# in module2.py from module1 import class1
это работает как в python 2 и 3.
если оба пакета находятся в пути импорта (sys.путь), и модуль / класс, который вы хотите находится в example/example.py, затем для доступа к классу без относительного импорта попробуйте:
from example.example import fkt
чтобы обойти эту проблему, я придумал решение с упаковка, который работал для меня в течение некоторого времени. Он добавляет верхний каталог к пути lib:
import repackage repackage.up() from mypackage.mymodule import myfunction
переупаковка может сделать относительный импорт, который работает в широком диапазоне случаев, используя интеллектуальную стратегию (проверка стека вызовов).
надеюсь, это будет полезно для кого - то там-я прошел через полдюжины сообщений stackoverflow, пытаясь выяснить относительный импорт, подобный тому, что было опубликовано выше здесь. Я настроил все, как предлагалось, но я все еще бил
ModuleNotFoundError: No module named 'my_module_name'
так как я просто развивался локально и играл вокруг, я не создал / запустить . Я также, по-видимому, не установил свой
PYTHONPATH
.я понял, что когда я запустил свой код, как я был, когда тесты были в том же каталоге, что и модуль, я не смог найти свой модуль:
$ python3 test/my_module/module_test.py 2.4.0 Traceback (most recent call last): File "test/my_module/module_test.py", line 6, in <module> from my_module.module import * ModuleNotFoundError: No module named 'my_module'
однако, когда я явно указал путь вещи начали работать:
$ PYTHONPATH=. python3 test/my_module/module_test.py 2.4.0 ........... ---------------------------------------------------------------------- Ran 11 tests in 0.001s OK
Итак, в случае, если кто-то попробовал несколько предложений, считает, что их код структурирован правильно и все еще находится в аналогичной ситуации, как и я, попробуйте любое из следующих действий, если вы не экспортируете текущий каталог в свой PYTHONPATH:
- Запустите свой код и явно включите путь следующим образом:
$ PYTHONPATH=. python3 test/my_module/module_test.py
- чтобы не звонить
PYTHONPATH=.
создатьsetup.py
файл с содержимым, как показано ниже, и запуститьpython setup.py development
чтобы добавить пакеты в путь:# setup.py from setuptools import setup, find_packages setup( name='sample', packages=find_packages() )
для меня мне нужно было запустить python3 из основного каталога, чтобы заставить его работать. Например, если проект имеет следующую структуру:
project_demo
| - main.py
| - - some_package
| - - - - __ init __ .ру
| - - - - project_configs.py
| - - тест
| - - - - test_project_configs.py
устранение: Я бы запустил python3 внутри project_demo а то из some_package import project_configs.