Относительно импорта в 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 - количество родительских каталогов (относительно каталога скрипта), в которых будет выполняться поиск импортируемого модуля.

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

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

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

  3. импортируйте Родительский модуль текущего модуля с помощью его полное название

  4. Set __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 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:

  1. добавить родительский каталог пакета 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
    
  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 рекомендует использовать абсолютный импорт, но заявляет, что в некоторых сценариях явный относительный импорт приемлем:

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


я столкнулся с этой проблемой. Обходной путь взлома импортируется через блок 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:

  1. запустите код и явно включите путь следующим образом: $ PYTHONPATH=. python3 test/my_module/module_test.py
  2. чтобы избежать вызова 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.