Относительно импорта в 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 на этом и на любом другом предложенном twiddlings из
__main__
механизм. Единственный вариант использования - это запуск сценариев, которые происходят чтобы жить внутри каталога модуля, который я всегда видел как антимодель. Чтобы заставить меня передумать, тебе придется убедить меня в этом. это не так.
является ли запуск скриптов внутри пакета антипаттерном или нет субъективным, но лично я считаю его очень полезным в пакете, который содержит некоторые пользовательские виджеты wxPython, поэтому я могу запустить скрипт для любой из исходных файлов для отображения wx.Frame
содержит только этот виджет для целей тестирования.
объяснение
с PEP 328
относительный импорт используйте атрибут __name _ _ модуля, чтобы определить, что положение модуля в иерархии пакетов. Если имя модуля делает не содержит никакой информации о пакете (например, он установлен в ' _ _ main__') затем относительный импорт разрешается, как если бы модуль был верхнего уровня модуль, независимо от того, где модуль на самом деле находится в файле система.
в какой-то момент PEP 338 противоречит PEP 328:
... относительный импорт зависит от __имя__ для определения текущего положение модуля в иерархии пакетов. В основном модуле, значение __имя__ всегда ' _ _ main__', поэтому явный относительный импорт всегда будет терпеть неудачу (поскольку они работают только для модуля внутри пакет)
и чтобы решить эту проблему,PEP 366 введена переменная верхнего уровня __package__
:
добавляя новый атрибут уровня модуля, этот PEP позволяет относительный импорт для работы автоматически, если модуль выполняется с помощью - m переключатель. Небольшое количество boilerplate в самом модуле позволит относительный импорт для работы, когда файл выполняется по имени. [...] Когда он [ attribute] присутствует, относительный импорт будет основан на этом атрибуте вместо модуля __имя__. [...] Когда основной модуль указан его именем файла, то __пакет__ атрибут будет установлен в нет. [...] когда система импорта обнаруживает явный относительный импорт в модуль без __пакет__ установить (или ее нет), это будет вычислить и сохранить правильное значение ( _ _ name__.rpartition ('.')[0] для нормальных модулей и __имя__ для модулей инициализации пакета)
(выделено мной)
если __name__
is '__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
(так же, как и выше) является пустой строкой, сообщение об ошибке будет
ImportError: attempted relative import with no known parent package
, вы увидите это только в Python 3.6 или новее.
Решение #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__
, но вы можете сделать это сами в the
решение #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 is
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:
-
добавить родительский каталог пакета to
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.path), и модуль / класс, который вы хотите, находится в example/example.py, затем для доступа к классу без относительного импорта попробуйте:
from example.example import fkt
чтобы устранить эту проблему, я разработал решение с помощью упаковка, который работал для меня в течение некоторого времени. Он добавляет верхний каталог к пути lib:
import repackage
repackage.up()
from mypackage.mymodule import myfunction
Repackage может выполнять относительный импорт, который работает в широком диапазоне случаев, используя интеллектуальную стратегию (проверка стека вызовов).
надеюсь, это будет иметь ценность для кого - то там-я прошел через полдюжины сообщений 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 импортировать project_configs.