Относительный импорт в 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 - это количество родительских каталогов (относительно каталога скрипта), в которых будет выполняться поиск импортируемого модуля.
Таким образом,
Добавьте родительский каталог 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 по имени файла:
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: Используйте абсолютный импорт и установочные инструменты
Шаги -
Заменить явный относительный импорт эквивалентным абсолютным импортом
устанавливать
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:
Добавить родительский каталог пакета в
sys.path
прежде чем пытаться импортировать что-либо из пакета, используя абсолютный импорт:import sys from pathlib import Path # if you haven't already done so file = Path(__file__).resolve() parent, root = file.parent, file.parents[1] sys.path.append(str(root)) # Additionally remove the current file's directory from sys.path try: sys.path.remove(str(parent)) except ValueError: # Already removed pass
Замените относительный импорт на абсолютный импорт:
from package import module # absolute import
standalone.py работает без проблем:
vaultah@base:~$ python3 -i package/standalone.py
Running /home/vaultah/package/standalone.py
Importing /home/vaultah/package/__init__.py
Importing /home/vaultah/package/module.py
>>> module
<module 'package.module' from '/home/vaultah/package/module.py'>
>>> import sys
>>> sys.modules['package']
<module 'package' from '/home/vaultah/package/__init__.py'>
>>> sys.modules['package.module']
<module 'package.module' from '/home/vaultah/package/module.py'>
Я чувствую, что должен предупредить вас: старайтесь не делать этого, особенно если ваш проект имеет сложную структуру.
В качестве примечания, PEP 8 рекомендует использовать абсолютный импорт, но утверждает, что в некоторых сценариях допустим явный относительный импорт:
Рекомендуется абсолютный импорт, так как они обычно более читабельны и, как правило, ведут себя лучше (или, по крайней мере, дают лучшие сообщения об ошибках). [...] Однако явный относительный импорт является приемлемой альтернативой абсолютному импорту, особенно когда речь идет о сложных макетах пакетов, где использование абсолютного импорта было бы излишне многословным.
Поместите это в файл вашего пакета __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:
- Запустите ваш код и явно укажите путь следующим образом:
$ 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() )
Мой шаблон для создания с относительным импортом в работоспособной автономной версии.
## 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
...
Теперь вы можете использовать свой модуль любым способом:
- Запускаем модуль как обычно:
python -m package.module
- Используйте его как модуль:
python -c 'from package import module'
- Запустите его в автономном режиме:
python package/module.py
- или с шебангом (
#!/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
.
Вот как я создал проект и его
import
s;
# 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
(который ничего не делает) и переходит к следующей строке
- Python: рассматривает это как неназначенную строку документации, начинающуюся с
- Строка 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
.
- Обходной путь: используйте WSL или сценарий пакетной оболочки для вызова
- Это работает, только если текущий рабочий каталог установлен на
/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())