Относительный импорт в 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
Я не понимаю логики здесь, и я не мог найти никакого объяснения. Это выглядит совершенно случайным.
Может кто-нибудь объяснить мне, что логика всего этого?
Ответы
Ответ 1
К сожалению, этот модуль должен находиться внутри пакета, а также иногда должен выполняться как script. Любая идея, как я мог достичь этого?
Совсем довольно иметь такой макет...
main.py
mypackage/
__init__.py
mymodule.py
myothermodule.py
... с a 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()
... a 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()
... и a 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
Это какая-то боль, но есть подсказка, почему в электронное письмо, написанное неким Guido van Rossum...
Я нахожусь на этом и на любых других предлагаемых твидлингах __main__
техника. Единственный случай использования, похоже, работает с сценариями, которые происходят чтобы жить внутри каталога модуля, который я всегда рассматривал как антипаттерн. Чтобы заставить меня передумать, вы должны были бы убедить меня, что это не так.
Является ли выполнение скриптов внутри пакета антипаттерном или нет, субъективно, но лично я считаю его полезным в пакете, который содержит некоторые пользовательские виджеты wxPython, поэтому я могу запустить script для любого из исходных файлов для отображения wx.Frame
, содержащего только этот виджет для целей тестирования.
Ответ 2
объяснение
От PEP 328
Относительный импорт использует атрибут модуля __name__, чтобы определить позицию этого модуля в иерархии пакетов. Если имя модуля не содержит никакой информации о пакете (например, оно установлено на "__main__"), то относительный импорт разрешается так, как если бы модуль был модулем верхнего уровня, независимо от того, где этот модуль фактически расположен в файловой системе.
В какой-то момент PEP 338 вступил в конфликт с PEP 328:
... относительный импорт полагается на __name__, чтобы определить текущую позицию модуля в иерархии пакетов. В главном модуле значение __name__ всегда равно __main__, поэтому явный относительный импорт всегда завершится неудачей (так как они работают только для модуля внутри пакета)
и для решения этой проблемы PEP 366 представил переменную верхнего уровня __package__
:
Добавляя новый атрибут уровня модуля, этот PEP позволяет относительному импорту работать автоматически, если модуль выполняется с использованием переключателя -m. Небольшое количество стандартного шаблона в самом модуле позволит относительному импорту работать, когда файл выполняется по имени. [...] Когда он [атрибут] присутствует, относительный импорт будет основываться на этом атрибуте, а не на атрибуте __name__ модуля. [...] Когда основной модуль указан в имени файла, атрибуту __package__ будет присвоено значение Нет. [...] Когда система импорта обнаруживает явный относительный импорт в модуле без установленного __package__ (или с установленным значением 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
(такой же, как выше) является пустой строкой, сообщение об ошибке будет
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
командной строки -m
который будет "искать sys.path
для указанного модуля и выполнять его содержимое как модуль __main__
":
[email protected]:~$ 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 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
выполняет все импортные -m
за вас и автоматически устанавливает __package__
, но вы можете сделать это самостоятельно в
Решение № 2: Установите __package__ вручную
Пожалуйста, рассматривайте это как доказательство концепции, а не фактическое решение. Он не очень подходит для использования в реальном коде.
PEP 366 имеет обходной путь к этой проблеме, однако он неполон, поскольку __package__
только __package__
недостаточно. Вам нужно будет импортировать как минимум N предыдущих пакетов в иерархии модулей, где N - это количество родительских каталогов (относительно каталога скрипта), в которых будет выполняться поиск импортируемого модуля.
Таким образом,
-
Добавьте родительский каталог N-го предшественника текущего модуля в sys.path
-
Удалить текущий каталог файлов из sys.path
-
Импортировать родительский модуль текущего модуля, используя его полное имя
-
Задайте для __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 по имени файла:
[email protected]:~$ 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
Шаги -
-
Заменить явный относительный импорт эквивалентным абсолютным импортом
-
Установите 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
установит пакет в каталог site-packages) (шаг 2):
[email protected]:~$ cd project
[email protected]:~/project$ python3 setup.py install --user
Давайте проверим, что теперь можно запустить standalone.py как скрипт:
[email protected]:~/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 directory from sys.path
try:
sys.path.remove(str(parent))
except ValueError: # Already removed
pass
-
Замените относительный импорт на абсолютный импорт:
from package import module # absolute import
standalone.py работает без проблем:
[email protected]:~$ 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 рекомендует использовать абсолютный импорт, но утверждает, что в некоторых сценариях допустим явный относительный импорт:
Рекомендуется абсолютный импорт, так как они обычно более читабельны и, как правило, ведут себя лучше (или, по крайней мере, дают лучшие сообщения об ошибках). [...] Однако явный относительный импорт является приемлемой альтернативой абсолютному импорту, особенно когда речь идет о сложных макетах пакетов, где использование абсолютного импорта было бы излишне многословным.
Ответ 3
Поместите это в ваш файл __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.
Ответ 4
Я столкнулся с этим вопросом. Временное решение для взлома - импорт через блок 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()
Ответ 5
Надеюсь, это будет полезно для кого-то там - я просмотрел полдюжины постов, посвященных стековому потоку, пытаясь выяснить относительный импорт, аналогичный тому, что был опубликован здесь выше. Я настроил все как предложено, но я все еще ModuleNotFoundError: No module named 'my_module_name'
Поскольку я просто разрабатывал локально и играл, я не создал/не запустил файл setup.py
. Я также, очевидно, не установил свою 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=. 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()
)
Ответ 6
Чтобы устранить эту проблему, я разработал решение с пакетом repackage, который работал для меня в течение некоторого времени. Это добавляет верхний каталог к пути lib:
import repackage
repackage.up()
from mypackage.mymodule import myfunction
Переупаковка может сделать относительный импорт, который работает в широком диапазоне случаев, используя интеллектуальную стратегию (проверка стека вызовов).
Ответ 7
Мне нужно было запустить python3 из основного каталога проекта, чтобы он работал.
Например, если проект имеет следующую структуру:
project_demo/
├── main.py
├── some_package/
│ ├── __init__.py
│ └── project_configs.py
└── test/
└── test_project_configs.py
Решение
Я бы запустил python3 внутри папки project_demo/, а затем выполнил бы
from some_package import project_configs
Ответ 8
если оба пакета находятся в вашем пути импорта (sys.path), а модуль/класс, который вы хотите, находится в примере /example.py, затем для доступа к классу без относительного импорта выполните:
from example.example import fkt
Ответ 9
Я думаю, что лучшее решение - создать пакет для вашего модуля:
Здесь - дополнительная информация о том, как это сделать.
Если у вас есть пакет, вам не нужно беспокоиться об относительном импорте, вы можете просто выполнить абсолютный импорт.