Относительный импорт в 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

Я не понимаю логику здесь, и я не мог найти никакого объяснения. Это выглядит совершенно случайно.

Может ли кто-нибудь объяснить мне, что за всем этим стоит логика?

33 ответа

Решение

к сожалению, этот модуль должен быть внутри пакета, и иногда его также нужно запускать как скрипт. Есть идеи, как мне этого добиться?

Это довольно распространено, чтобы иметь такой макет...

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

... но это несколько многословно, и не очень хорошо сочетается с линией Шебанга, как #!/usr/bin/env python3,

Самое простое решение для этого случая, принимая имя mymodule глобально уникален, будет избегать использования относительного импорта, и просто использовать...

from mymodule import as_int

... хотя, если он не уникален или структура вашего пакета более сложная, вам нужно включить каталог, содержащий каталог вашего пакета, в PYTHONPATHи делай так...

from mypackage.mymodule import as_int

... или если вы хотите, чтобы он работал "из коробки", вы можете 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__ модуля. [...] Когда основной модуль указан в имени файла, атрибуту __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 опция командной строки, которая будет "искать" 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: Установите __package__ вручную

Пожалуйста, рассматривайте это как доказательство концепции, а не фактическое решение. Он не очень подходит для использования в реальном коде.

PEP 366 имеет обходной путь к этой проблеме, однако он неполон, поскольку настройка __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 по имени файла:

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: Используйте абсолютный импорт и установочные инструменты

Шаги -

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

vaultah@base:~$ cd project
vaultah@base:~/project$ python3 setup.py install --user

Давайте проверим, что теперь можно запускать standalone.py как скрипт:

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

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

Поместите это в файл вашего пакета __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.

Я столкнулся с этим вопросом. Временное решение для взлома - импорт через блок 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()

SystemError: родительский модуль '' не загружен, не может выполнить относительный импорт

Это означает, что вы запускаете модуль внутри пакета как сценарий. Смешивание скриптов внутри пакетов - дело сложное, и его следует по возможности избегать. Используйте сценарий оболочки, который импортирует пакет и запускает ваш scripty функция вместо этого.

Если вызывается ваш каталог верхнего уровня, который находится на вашем PYTHONPATH путь поиска модуля, и у вас есть пакет (это каталог, который вы ожидаете __init__.pyфайл в), скрипты не должны быть помещены внутрь , но должны жить в foo в лучшем случае .

Обратите внимание, что скрипты здесь отличаются от модулей тем, что они используются в качестве аргумента имени файла для python команда, либо с помощью python <filename> или через #!(шебанг) ​​линия. Он загружается напрямую как модуль (поэтому if __name__ == "__main__": работает в сценариях), и нет контекста пакета, который можно было бы использовать для относительного импорта.

Ваши варианты

  • Если можете, упакуйте свой проект с помощью (или или, что может помочь упростить упаковку) и создайте точки входа в консольный скрипт ; установка вашего проекта с pipзатем создает сценарии, которые знают, как правильно импортировать ваш пакет. Вы можете установить свой пакет локально с помощью pip install -e ., поэтому его все еще можно редактировать на месте.

  • В противном случае никогда и никогда не используйте python path/to/packagename/file.py, всегда используйте python path/to/script.py а также script.py можешь использовать from packagename import ....

  • В качестве запасного варианта вы можете использовать -mпереключатель командной строки, указывающий Python импортировать модуль и использовать его в качестве файла. Это не работает со строкой shebang, поскольку файла сценария больше нет.

    Если вы используете python -m foo.bar а также foo/bar.py находится в каталоге, который затем импортируется и выполняется как __main__с правильным контекстом пакета. Если bar тоже пакет, внутри foo/, он должен иметь __main__.py файл (так foo/bar/__main__.py как путь из каталога).

  • В крайних случаях добавьте метаданные, которые Python использует для разрешения относительного импорта, установив __package__напрямую; файл foo/bar/spam.py, импортируется как foo.bar.spam, дается глобальный __package__ = "foo.bar". Это просто еще один глобальный, вроде и __name__, устанавливается Python при импорте.

На

Все вышесказанное требует, чтобы ваш пакет можно было импортировать, а это значит, что он должен быть найден в одном из каталогов (или zip-файлов), перечисленных в . Здесь тоже есть несколько вариантов:

  • Каталог, в котором path/to/script.pyбыл найден (поэтому) автоматически добавляется в. Выполнение python path/to/foo.py добавляет path/to к .

  • Если вы упаковали свой проект (с setuptools, poetry, flit или другой инструмент упаковки Python) и установил его, пакет уже был добавлен в нужное место.

  • В крайнем случае добавьте правильный каталог в sys.pathсам. Если пакет может быть расположен относительно файла сценария, используйте __file__ переменная в глобальном пространстве имен скрипта (например, с помощью pathlib.Pathобъект , HERE = Path(__file__).resolve().parent ссылка на каталог, в котором находится файл, как абсолютный путь).

TL;DR

Вы можете относительно импортировать модули только внутри другого модуля в том же пакете.

Уточнение концепции

Мы видим много примеров кода в книгах/документах/статьях, они показывают нам, как относительно импортировать модуль, но когда мы это делаем, это не работает.

Причина в том, что, выражаясь простым предложением, мы не запустили код так, как этого ожидает модульный механизм Python, даже несмотря на то, что код написан совершенно правильно. Это похоже на какую-то вещь во время выполнения.

Загрузка модуля зависит от того, как вы запускаете код. Это источник путаницы.

Что такое модуль?

Модуль является файлом Python тогда и только тогда, когда он импортируется другим файлом. Учитывая файлmod.py, это модуль? Да и Нет, если вы бежитеpython mod.py, это не модуль, потому что он не импортируется.

Что такое пакет?

Пакет — это папка, содержащая модули Python.

Кстати, в python 3.3 нет необходимости, если вам не нужна инициализация пакета или подмодули автоматической загрузки. Вам не нужно размещать пустой__init__.pyв каталоге.

Это доказывает, что пакет — это просто папка, пока есть импортируемые файлы.


Настоящий ответ

Теперь это описание становится более ясным.

Вы можете относительно импортировать модули только внутри другого модуля в том же пакете.

Учитывая каталог:

      . CWD
|-- happy_maker.py # content: print('Sends Happy')
`-- me.py # content: from . import happy_maker

Бегатьpython me.py, у нас естьattempted relative import with no known parent package

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

Решение 1

Использоватьimport happy_makerвместоfrom . import happy_maker

Решение 2

Переключите нашу рабочую директорию в родительскую папку.

      . CWD
|-- happy
|   |-- happy_maker.py
    `-- me.py

Бегатьpython -m happy.me.

Когда мы находимся в каталоге, который включает в себя пакет,me.py, happy_maker.pyявляются модулями, теперь мы можем использовать относительный импорт, и мы все еще хотим запускать, поэтому мы используем-mчто означает запуск модуля как скрипта.


Идиома Python

      . CWD
|-- happy
|   |-- happy_maker.py # content: print('Sends Happy')
|   `-- me.py # content: from . import happy_maker
`-- main.py # content: import happy.me

Эта структура является идиомой Python.mainнаш скрипт, лучшая практика в Python. Наконец, мы добрались туда.


Братья и сестры или дедушки и бабушки

Еще одна распространенная потребность:

      .
|-- happy
|   |-- happy_maker.py
|   `-- me.py
`-- sad
    `-- sad_maker.py

Мы хотим импортироватьsad_makerв , Как это сделать?

Во-первых, нам нужно сделатьhappyиsadв том же пакете, поэтому нам нужно подняться на уровень каталога. А потомfrom ..sad import sad_makerвme.py.

Вот и все.

Для пользователей PyCharm:

Я также получал ImportError: attempted relative import with no known parent package потому что я добавлял .обозначение, чтобы заглушить ошибку синтаксического анализа PyCharm. PyCharm неточно сообщает, что не может найти:

lib.thing import function

Если вы измените его на:

.lib.thing import function

он заглушает ошибку, но затем вы получаете вышеупомянутый ImportError: attempted relative import with no known parent package. Просто игнорируйте парсер PyCharm. Это неправильно, и код работает нормально, несмотря на то, что он говорит.

TL;DR: на ответ @Aya, обновленный с помощью pathlib библиотека и работа с записными книжками Jupyter, где __file__ не определен:

Вы хотите импортировать my_function определено в ../my_Folder_where_the_package_lives/my_package.py уважение к тому, где вы пишете код.

Затем сделайте:

      import os
import sys
import pathlib

PACKAGE_PARENT = Path.cwd().parent
SCRIPT_DIR = PACKAGE_PARENT / "my_Folder_where_the_package_lives"
sys.path.append(str(SCRIPT_DIR))

from my_package import my_function

Чтобы устранить эту проблему, я разработал решение с пакетом repackage, который работал для меня в течение некоторого времени. Это добавляет верхний каталог к ​​пути lib:

import repackage
repackage.up()
from mypackage.mymodule import myfunction

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

Надеюсь, это будет полезно для кого-то там - я просмотрел полдюжины постов, посвященных стековому потоку, пытаясь выяснить относительный импорт, аналогичный тому, что был опубликован здесь выше. Я настроил все как предложено, но я все еще бил 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
  2. Чтобы не звонить PYTHONPATH=., создать setup.py файл с содержимым, подобным следующему, и запустите python setup.py development чтобы добавить пакеты к пути:
# setup.py
from setuptools import setup, find_packages

setup(
    name='sample',
    packages=find_packages()
)

Мой шаблон для создания с относительным импортом в работоспособной автономной версии.

      ## Standalone boilerplate before relative imports
if __package__ is None:                  
    DIR = Path(__file__).resolve().parent
    sys.path.insert(0, str(DIR.parent))
    __package__ = DIR.name

from . import variable_in__init__py
from . import other_module_in_package
...

Теперь вы можете использовать свой модуль любым способом:

  1. Запускаем модуль как обычно: python -m package.module
  2. Используйте его как модуль: python -c 'from package import module'
  3. Запустите его в автономном режиме: python package/module.py
  4. или с шебангом ( #!/bin/env python) только: package/module.py

NB!С помощью sys.path.append вместо sys.path.insert будет трудно отследить ошибку, если ваш module имеет то же имя, что и ваш package. Например my_script/my_script.py

Конечно, если у вас есть относительный импорт с более высоких уровней в иерархии пакетов, этого недостаточно, но в большинстве случаев это нормально.

Я получал это ImportError: попытка относительного импорта без известного родительского пакета

В моей программе я использовал файл из текущего пути для импорта его функции.

from .filename import function

Затем я изменил текущий путь (точка) на имя пакета. Что решило мою проблему.

from package_name.filename import function

Я надеюсь, что приведенный выше ответ вам поможет.

Импорт из того же каталога

Во-первых, вы можете импортировать из того же каталога.

Вот файловая структура ...

      Folder
 |
 ├─ Scripts
 |   ├─ module123.py
 |
 ├─ main.py
 ├─ script123.py

Вот main.py

      from . import script123
from Scripts import module123

Как видите, импорт из . импорт из текущего каталога.

Примечание: при запуске с использованием чего-либо, кроме IDLE, перед запуском убедитесь, что ваш терминал находится в том же каталоге, что и файл.

Также работает импорт из локальной папки.

Импорт из родительского каталога

Как видно из моей статьи на GitHub , существует следующий метод.

Возьмите следующее дерево файлов ...

      ParentDirectory
 ├─ Folder
 |   |
 |   ├─ Scripts
 |   |   ├─ module123.py
 |   |
 |   ├─ main.py
 |   ├─ script123.py
 |
 ├─ parentModule.py

Затем просто добавьте этот код в верхнюю часть вашего main.py файл.

      import inspect
import os
import sys

current_dir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
parent_dir = os.path.dirname(current_dir)
sys.path.insert(0, parent_dir)

from ParentDirectory import Stuff

Мне нужно было запустить python3 из основного каталога, чтобы он работал. Например, если проект имеет следующую структуру:


project_demo

| - main.py

| - - Some_package

| - - - - __ init __.py

| - - - - project_configs.py

| - - тестовое задание

| - - - - test_project_configs.py


Решение: я бы запустил python3 внутри project_demo, а затем из some_package import project_configs.

Я попробовал все вышеперечисленное безрезультатно, только чтобы понять, что я ошибочно использовал в имени моего пакета.

Короче говоря, нет - в каталоге, где __init__.pyявляется. Я никогда не испытывал восторга после того, как обнаружил такую ​​глупость.

Вот три строчки для тех, кто не согласен с Гвидо :

      import sys
from pathlib import Path
sys.path.append(str(Path(sys.argv[0]).absolute().parent.parent))

Надеюсь, поможет.

Если ничего из вышеперечисленного не помогло, вы можете указать модуль явно.

Справочник:

├── Project
│     ├── Dir
│     │    ├── __init__.py
│     │    ├── module.py
│     │    └── standalone.py

Решение:

#in standalone.py
from Project.Dir.module import ...

module - модуль для импорта

Если оба пакета находятся в вашем пути импорта (sys.path), а нужный вам модуль / класс находится в example/example.py, тогда для доступа к классу без относительного импорта попытайтесь:

from example.example import fkt

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

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

Я часто сталкиваюсь с этим, когда работаю с Django, поскольку многие функции выполняются из сценария, но я также хочу, чтобы некоторые из моих модулей запускались непосредственно как сценарии (в идеале вы должны сделать их директивами, но мы не там еще нет).

Это макет того, как может выглядеть такой проект;

      ├── dj_app
│   ├── models.py
│   ├── ops
│   │   ├── bar.py
│   │   └── foo.py
│   ├── script.py
│   ├── tests.py
│   ├── utils.py
│   └── views.py
└── manage.py

Важными частями здесь являются, и. У нас также есть подмодули dj_app/ops/bar.py а также dj_app/ops/foo.py которые содержат больше элементов, которые мы хотим использовать на протяжении всего проекта.

Источником проблемы обычно является желание вашего dj_app/script.py методы сценария, чтобы иметь тестовые примеры в dj_app/tests.py которые вызываются при запуске manage.py test.

Вот как я создал проект и его imports;

      # dj_app/ops/foo.py
# Foo operation methods and classes
foo_val = "foo123"

.

      # dj_app/ops/bar.py
# Bar operations methods and classes
bar_val = "bar123"

.

      # dj_app/script.py
# script to run app methods from CLI

# if run directly from command line
if __name__ == '__main__':
    from ops.bar import bar_val
    from ops.foo import foo_val

# otherwise
else:
    from .ops.bar import bar_val
    from .ops.foo import foo_val

def script_method1():
    print("this is script_method1")
    print("bar_val: {}".format(bar_val))
    print("foo_val: {}".format(foo_val))


if __name__ == '__main__':
    print("running from the script")
    script_method1()

.

      # dj_app/tests.py
# test cases for the app
# do not run this directly from CLI or the imports will break
from .script import script_method1
from .ops.bar import bar_val
from .ops.foo import foo_val 

def main():
    print("Running the test case")
    print("testing script method")
    script_method1()

if __name__ == '__main__':
    print("running tests from command line")
    main()

.

      # manage.py
# just run the test cases for this example
import dj_app.tests
dj_app.tests.main()

.

Запуск тестовых случаев из manage.py;

      $ python3 manage.py
Running the test case
testing script method
this is script_method1
bar_val: bar123
foo_val: foo123

Самостоятельный запуск скрипта;

      $ python3 dj_app/script.py
running from the script
this is script_method1
bar_val: bar123
foo_val: foo123

Обратите внимание, что вы получите сообщение об ошибке, если попытаетесь запустить test.py однако напрямую, так что не делайте этого;

      $ python3 dj_app/tests.py
Traceback (most recent call last):
  File "dj_app/tests.py", line 5, in <module>
    from .script import script_method1
ModuleNotFoundError: No module named '__main__.script'; '__main__' is not a package

Если я сталкиваюсь с более сложными ситуациями с импортом, я обычно в конечном итоге использую что-то вроде этого, чтобы взломать его;

      import os
import sys
THIS_DIR = os.path.dirname(os.path.realpath(__file__))
sys.path.insert(0, THIS_DIR)
from script import script_method1
sys.path.pop(0)

Приятно отметить, что иногда кеш вызывает все это - я пробовал разные вещи после перераспределения классов в новые каталоги, и относительный импорт начал работать после того, как я удалил__pycache__

Это моя структура проекта

      ├── folder
|   | 
│   ├── moduleA.py
|   |   |
|   |   └--function1()
|   |       └~~ uses function2()
|   | 
│   └── moduleB.py
|       | 
|       └--function2()
|   
└── main.py
     └~~ uses function1()

Здесь мой импорт и main импорт

Я добавил приведенный ниже фрагмент в moduleA импортировать moduleB

      try:
    from .moduleB import function2 
except:
    from moduleB import function2 

Теперь я могу выполнить оба main.py также как и moduleA.py индивидуально

Это решение?

Я создал новую экспериментальную библиотеку импорта для Python: ultraimport

Это дает программисту больше контроля над импортом и делает его однозначным. Также это дает лучшие сообщения об ошибках, когда импорт терпит неудачу.

Это позволяет вам выполнять относительный импорт на основе файловой системы, который всегда работает, независимо от того, как вы запускаете свой код и какой у вас текущий рабочий каталог. Неважно, запускаете ли вы скрипт или модуль. Вам также не нужно изменять sys.path, который может иметь другие побочные эффекты.

Вы бы тогда изменили

      from .mymodule import myfunction

к

      import ultraimport
myfunction = ultraimport('__dir__/mymodule.py', 'myfunction')

Таким образом, импорт всегда будет работать, даже если вы запустите код как скрипт.

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

Приведенное ниже решение протестировано на Python3.

      ├── classes
|   |
|   ├──__init__.py
|   | 
│   ├── userclass.py
|   |   |
|   |   └--viewDetails()
|   |       
|   | 
│   └── groupclass.py
|       | 
|       └--viewGroupDetails()
|   
└── start.py
     └~~ uses function1()

Теперь, чтобы использовать viewDetails пользовательского класса или viewGroupDetails группового класса, сначала определите это в _ init _.py каталога classs.

Пример: в _ init _.py

      from .userclasss import viewDetails

from .groupclass import viewGroupDetails

Шаг 2: Теперь в start.py мы можем напрямую импортировать viewDetails.

Пример: в start.py

      from classes import viewDetails
from classes import viewGroupDetails

Я получал ту же ошибку, и структура моего проекта была похожа на

      ->project
  ->vendors
    ->vendors.py
  ->main.py

Я пытался позвонить так

      from .vendors.Amazon import Amazom_Purchase

Здесь выдавало ошибку, поэтому я исправил ее, просто удалив первый файл . из заявления

      from vendors.Amazon import Amazom_Purchase

Надеюсь это поможет.

Я столкнулся с аналогичной проблемой при попытке написать файл Python, который можно загрузить либо как модуль, либо как исполняемый скрипт.

Настраивать

      /path/to/project/
├── __init__.py
└── main.py
    └── mylib/
        ├── list_util.py
        └── args_util.py

с:

main.py:

      #!/usr/bin/env python3
import sys
import mylib.args_util

if __name__ == '__main__':
    print(f'{mylib.args_util.parseargs(sys.argv[1:])=}')

mylib/list_util.py:

      def to_int_list(args):
    return [int(x) for x in args]

      #!/usr/bin/env python3
import sys
from . import list_util as lu

def parseargs(args):
    return sum(lu.to_int_list(args))

if __name__ == '__main__':
    print(f'{parseargs(sys.argv[1:])=}')

Выход

      $ ./main.py 1 2 3
mylib.args_util.parseargs(sys.argv[1:])=6

$ mylib/args_util.py 1 2 3
Traceback (most recent call last):
  File "/path/to/project/mylib/args_util.py", line 10, in <module>
    from . import list_util as lu
ImportError: attempted relative import with no known parent package

Решение

Я остановился на полиглот-решении Bash/Python. Bash-версия программы просто вызывает, а затем завершает работу.

Версия Python игнорирует код Bash, поскольку он содержится в строке документации.

Версия Bash игнорирует код Python, поскольку используетexecчтобы остановить синтаксический анализ/бегущие строки.

mylib/args_util.py:

      #!/bin/bash
# -*- Mode: python -*-
''''true
exec /usr/bin/env python3 -m mylib.args_util "$@"
'''

import sys
from . import list_util as lu

def parseargs(args):
    return sum(lu.to_int_list(args))

if __name__ == '__main__':
    print(f'{parseargs(sys.argv[1:])=}')

Выход

      $ ./main.py 1 2 3
mylib.args_util.parseargs(sys.argv[1:])=6

$ mylib/args_util.py 1 2 3
parseargs(sys.argv[1:])=6

Объяснение

  • Линия 1:#!/bin/bash; это линия «шебанг»; он сообщает интерактивной оболочке, как запустить этот скрипт.
    • Python: игнорируется (комментарий)
    • Баш: игнорируется (комментарий)
  • Строка 2:# -*- Mode: python -*-необязательный; это называется «линия моды»; он говорит Emacs использовать подсветку синтаксиса Python вместо того, чтобы догадываться, что язык — Bash, при чтении файла.
    • Python: игнорируется (комментарий)
    • Баш: игнорируется (комментарий)
  • Строка 3:''''true
    • Python: рассматривает это как неназначенную строку документации, начинающуюся с'true\n
    • Bash: рассматривает это как три строки (из которых первые две пустые строки), которые расширяются до (т.е.'' + '' + 'true' = 'true'); затем он запускаетсяtrue(который ничего не делает) и переходит к следующей строке
  • Строка 4:exec /usr/bin/env python3 -m mylib.args_util "$@"
    • Python: по-прежнему рассматривает это как часть строки документации из строки 3.
    • Баш: бежитpython3 -m mylib.args_utilзатем выходит (он ничего не анализирует за этой строкой)
  • Строка 5:'''
    • Python: рассматривает это как конец строки документации из строки 3.
    • Bash: не анализирует эту строку

Предостережения

  • Это не работает в Windows:
    • Обходной путь: используйте WSL или сценарий пакетной оболочки для вызоваpython -m mylib.args_util.
  • Это работает, только если текущий рабочий каталог установлен на/path/to/project/.
    • Обходной путь: установитьPYTHONPATHпри звонке/usr/bin/env
                #!/bin/bash
      # -*- Mode: python -*-
      ''''true
      exec /usr/bin/env python3 \
          PYTHONPATH="$(cd "$(dirname "$0")/.." ; pwd)" \
          -m mylib.args_util "$@"
      '''
      

TL; DR; Добавьте путь к сценарию к системному пути, добавив следующее в точку входа вашего сценария python.

import os.path
import sys
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)))

Вот и все, теперь вы можете запускать свой проект как в PyCharma, так и из Терминала!!

Помогает перемещение файла, из которого вы импортируете, во внешний каталог.
Это особенно полезно, когда ваш основной файл создает любые другие файлы в своем собственном каталоге.
Пример:
До:
Project
| --- dir1
| ------- main.py
| ------- module1.py
После:
Project
| --- module1.py
| --- dir1
| - -----main.py

У меня была аналогичная проблема: мне нужен был сервис Linux и плагин cgi, которые для взаимодействия используют общие константы. "Естественный" способ сделать это - поместить их в init.py пакета, но я не могу запустить плагин cgi с параметром -m.

Мое окончательное решение было похоже на Решение № 2 выше:

import sys
import pathlib as p
import importlib

pp = p.Path(sys.argv[0])
pack = pp.resolve().parent

pkg = importlib.import_module('__init__', package=str(pack))

Недостатком является то, что перед константами (или общими функциями) необходимо ставить pkg:

print(pkg.Glob)

Если следующий импорт:

      from . import something 

не работает для вас, потому что это импорт упаковки python и не будет работать с вашей обычной реализацией, и вот пример, показывающий, как его использовать:

Структура папки:

      .
└── funniest
    ├── funniest
    │   ├── __init__.py
    │   └── text.py
    ├── main.py
    └── setup.py 

внутри__init__.pyдобавлять:

      def available_module(): 
    return "hello world"

text.pyдобавлять:

      from . import available_module

внутриsetup.pyдобавлять

      from setuptools import setup

setup(name='funniest',
  version='0.1',
  description='The funniest joke in the world',
  url='http://github.com/storborg/funniest',
  author='Flying Circus',
  author_email='flyingcircus@example.com',
  license='MIT',
  packages=['funniest'],
  zip_safe=False)

Теперь это самая важная часть, которая вам нужна для установки вашего пакета:

      pip install .

В любом другом месте нашей системы, использующем тот же Python, мы можем сделать это сейчас:

      >> import funnies.text as fun
>> fun.available_module() 

Это должно вывести «привет мир»

вы можете проверить это в (это не потребует установки пакета)

Вотmain.pyтакже

      import funniest.text as fun 
print(fun.available_module())
Другие вопросы по тегам