Абсолютный и относительный импорт в Python
Оглавление
Если вы работали над проектом на Python, содержащим более одного файла, то, скорее всего, вам уже приходилось использовать оператор import.
Даже для питонистов с парой проектов за плечами импорт может быть запутанным! Возможно, вы читаете эту статью, потому что хотите глубже понять импорт в Python, особенно абсолютный и относительный импорт.
В этом руководстве вы узнаете о различиях между ними, а также об их плюсах и минусах. Давайте погрузимся в процесс!
Краткая информация об импорте
Чтобы понять, как работает импорт, нужно хорошо разбираться в модулях и пакетах Python. Модуль Python - это файл с расширением .py
, а пакет Python - это любая папка, в которой есть модули (или, в Python 2, папка, содержащая файл __init__.py
).
Что происходит, когда в одном модуле есть код, который должен обращаться к коду в другом модуле или пакете? Вы импортируете его!
Как работает импорт
Но как именно работает импорт? Допустим, вы импортируете модуль abc
следующим образом:
import abc
Первое, что сделает Python, это найдет имя abc
в sys.modules
. Это кэш всех модулей, которые были импортированы ранее.
Если имя не найдено в кэше модулей, Python перейдет к поиску по списку встроенных модулей. Это модули, которые поставляются вместе с Python и находятся в стандартной библиотеке Python Standard Library. Если имя все еще не найдено во встроенных модулях, Python ищет его в списке каталогов, заданном командой sys.path
. В этот список обычно входит текущий каталог, который просматривается первым.
Когда Python находит модуль, он привязывает его к имени в локальной области видимости. Это означает, что abc
теперь определен и может быть использован в текущем файле без выброса NameError
.
Если имя так и не найдено, вы получите ModuleNotFoundError
. Подробнее об импорте можно узнать из документации по Python здесь!
Примечание: вопросы безопасности
Помните, что система импорта в Python сопряжена со значительными рисками для безопасности. Во многом это связано с ее гибкостью. Например, кэш модулей доступен для записи, и с помощью системы импорта можно переопределить основные функции Python. Импорт из пакетов сторонних разработчиков также может подвергнуть ваше приложение угрозам безопасности.
Вот несколько интересных ресурсов, позволяющих узнать больше об этих проблемах безопасности и способах их решения:
- 10 распространенных проблем безопасности в Python и как их избежать от Энтони Шоу (Пункт 5 рассказывает о системе импорта Python.)
- Эпизод #168: 10 дыр в безопасности Python и как их заткнуть из подкаста TalkPython (Участники дискуссии начинают говорить об импорте примерно с отметки 27:15.)
Синтаксис утверждений импорта
Теперь, когда вы знаете, как работают операторы импорта, давайте изучим их синтаксис. Вы можете импортировать как пакеты, так и модули. (Обратите внимание, что импорт пакета по сути импортирует __init__.py
файл пакета как модуль). Вы также можете импортировать определенные объекты из пакета или модуля.
Обычно существует два типа синтаксиса импорта. При использовании первого вы импортируете ресурс напрямую, например, так:
import abc
abc
может быть пакетом или модулем.
Когда вы используете второй синтаксис, вы импортируете ресурс из другого пакета или модуля. Вот пример:
from abc import xyz
xyz
может быть модулем, подпакетом или объектом, таким как класс или функция.
Вы также можете выбрать переименование импортированного ресурса, например, так:
import abc as other_name
Это переименование импортированного ресурса abc
в other_name
внутри сценария. Теперь на него нужно ссылаться как на other_name
, иначе он не будет распознан.
Стилизация утверждений импорта
PEP 8, официальное руководство по стилю Python, содержит несколько указаний по написанию операторов импорта. Вот краткое изложение:
-
Импорты всегда должны быть записаны в верхней части файла, после любых комментариев модуля и докстрингов.
-
Импорт должен быть разделен в зависимости от того, что импортируется. Обычно выделяют три группы:
- импорт стандартных библиотек (встроенных модулей Python)
- связанные сторонние импорты (модули, которые установлены и не относятся к текущему приложению)
- импорт локального приложения (модули, принадлежащие текущему приложению)
-
Каждая группа импортов должна быть разделена пустой строкой.
Также полезно упорядочить импорт в алфавитном порядке в каждой группе импорта. Это значительно облегчает поиск конкретных импортов, особенно если в файле много импортов.
Вот пример того, как стилизовать операторы импорта:
"""Illustration of good import statement styling.
Note that the imports come after the docstring.
"""
# Standard library imports
import datetime
import os
# Third party imports
from flask import Flask
from flask_restful import Api
from flask_sqlalchemy import SQLAlchemy
# Local application imports
from local_module import local_class
from local_package import local_function
Приведенные выше операторы импорта делятся на три отдельные группы, разделенные пустым пространством. Они также упорядочены в алфавитном порядке внутри каждой группы.
Абсолютный импорт
Вы узнали, как писать операторы импорта и как стилизовать их, как профессионал. Теперь пришло время узнать немного больше об абсолютном импорте.
Абсолютный импорт указывает ресурс, который должен быть импортирован, используя его полный путь из корневой папки проекта.
Синтаксис и практические примеры
Допустим, у вас следующая структура каталогов:
└── project
├── package1
│ ├── module1.py
│ └── module2.py
└── package2
├── __init__.py
├── module3.py
├── module4.py
└── subpackage1
└── module5.py
Имеется каталог project
, который содержит два подкаталога package1
и package2
. В каталоге package1
есть два файла, module1.py
и module2.py
.
В каталоге package2
находятся три файла: два модуля, module3.py
и module4.py
, и файл инициализации, __init__.py
. Он также содержит каталог subpackage
, который, в свою очередь, содержит файл module5.py
.
Предположим следующее:
package1/module2.py
содержит функцию,function1
.package2/__init__.py
содержит класс,class1
.package2/subpackage1/module5.py
содержит функцию,function2
.
Ниже приведены практические примеры абсолютного импорта:
from package1 import module1
from package1.module2 import function1
from package2 import class1
from package2.subpackage1.module5 import function2
Обратите внимание, что вы должны указать подробный путь для каждого пакета или файла, начиная с папки пакета верхнего уровня. Этот путь похож на путь к файлу, но вместо косой черты (/
) используется точка (.
).
Плюсы и минусы абсолютного импорта
Абсолютный импорт предпочтительнее, потому что он достаточно ясен и понятен. Легко определить, где именно находится импортируемый ресурс, просто взглянув на оператор. Кроме того, абсолютный импорт сохраняет свою силу, даже если текущее местоположение оператора импорта изменится. На самом деле, PEP 8 прямо рекомендует абсолютный импорт.
Однако иногда абсолютный импорт может быть довольно многословным, в зависимости от сложности структуры каталогов. Представьте себе такое утверждение:
from package1.subpackage2.subpackage3.subpackage4.module5 import function6
Это же смешно, правда? К счастью, относительный импорт - хорошая альтернатива в таких случаях!
Относительный импорт
Относительный импорт указывает ресурс, который должен быть импортирован, относительно текущего местоположения - то есть местоположения, в котором находится оператор импорта. Существует два типа относительного импорта: неявный и явный. Неявный относительный импорт был упразднен в Python 3, поэтому я не буду рассматривать его здесь.
Синтаксис и практические примеры
Синтаксис относительного импорта зависит от текущего местоположения, а также от местоположения импортируемого модуля, пакета или объекта. Вот несколько примеров относительного импорта:
from .some_module import some_class
from ..some_package import some_function
from . import some_class
Вы видите, что в каждом из приведенных выше операторов импорта есть как минимум одна точка. Относительный импорт использует точечную нотацию для указания местоположения.
Одиночная точка означает, что модуль или пакет, на который ссылаются, находится в том же каталоге, что и текущее местоположение. Две точки означают, что он находится в родительском каталоге текущего местоположения - то есть в каталоге выше. Три точки означают, что он находится в каталоге-прародителе, и так далее. Вам это, вероятно, знакомо, если вы используете Unix-подобную операционную систему!
Предположим, что у вас такая же структура каталогов, как и раньше:
└── project
├── package1
│ ├── module1.py
│ └── module2.py
└── package2
├── __init__.py
├── module3.py
├── module4.py
└── subpackage1
└── module5.py
Вызовите содержимое файла:
package1/module2.py
содержит функцию,function1
.package2/__init__.py
содержит класс,class1
.package2/subpackage1/module5.py
содержит функцию,function2
.
Вы можете импортировать function1
в файл package1/module1.py
таким образом:
# package1/module1.py
from .module2 import function1
Здесь используется только одна точка, потому что module2.py
находится в том же каталоге, что и текущий модуль, а это module1.py
.
Вы можете импортировать class1
и function2
в файл package2/module3.py
таким образом:
# package2/module3.py
from . import class1
from .subpackage1.module5 import function2
В первом операторе import одиночная точка означает, что вы импортируете class1
из текущего пакета. Помните, что импорт пакета по сути импортирует __init__.py
файл пакета как модуль.
Во втором операторе import снова используется одиночная точка, потому что subpackage1
находится в том же каталоге, что и текущий модуль, а это module3.py
.
Плюсы и минусы относительного импорта
Одно из очевидных преимуществ относительного импорта заключается в том, что он достаточно лаконичен. В зависимости от текущего местоположения, они могут превратить нелепо длинный оператор импорта, который вы видели ранее, в нечто простое, как это:
from ..subpackage4.module5 import function6
К сожалению, относительный импорт может быть неудобным, особенно в общих проектах, где структура каталогов может меняться. Относительный импорт также не так удобен для чтения, как абсолютный, и нелегко определить местоположение импортированных ресурсов.
Заключение
Молодцы, что дошли до конца этого краткого курса по абсолютному и относительному импорту! Теперь вы знаете, как работает импорт. Вы узнали о лучших практиках написания операторов импорта и знаете разницу между абсолютным и относительным импортом.
Получив новые навыки, вы сможете уверенно импортировать пакеты и модули из стандартной библиотеки Python, сторонних пакетов и своих собственных локальных пакетов. Помните, что обычно следует выбирать абсолютный импорт, а не относительный, если только путь не является сложным и не делает оператор слишком длинным.
Спасибо, что прочитали!
Вернуться на верх