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

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

  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 по имени файла:

[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

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

Шаги -

  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 установит пакет в каталог 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:

  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 directory from sys.path
    try:
        sys.path.remove(str(parent))
    except ValueError: # Already removed
        pass
    
  2. Замените относительный импорт на абсолютный импорт:

    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:

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

Ответ 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

Я думаю, что лучшее решение - создать пакет для вашего модуля: Здесь - дополнительная информация о том, как это сделать.

Если у вас есть пакет, вам не нужно беспокоиться об относительном импорте, вы можете просто выполнить абсолютный импорт.