Как работают импорты в Python
Импорты не так просты, как кажется. Тем более если они пишутся для двух несовместимых версий языка. Разбираемся с основными проблемами и их решением.
282К открытий296К показов
Порой бывает трудно правильно реализовать import
с первого раза, особенно если мы хотим добиться правильной работы на плохо совместимых между собой версиях Python 2 и Python 3. Попытаемся разобраться, что из себя представляют импорты в Python и как написать решение, которое подойдёт под обе версии языка.
Содержание
- Ключевые моменты
- Основные определения
- Пример структуры директорий
- Что делает import
- Основы import и sys.path
- Чуть подробнее о sys.path
- Всё о __init__.py
- Использование объектов из импортированного модуля или пакета
- Используем dir() для исследования содержимого импортированного модуля
- Импортирование пакетов
- Абсолютный и относительный импорт
- Примеры
- Python 2 vs Python 3
- Прочие темы, не рассмотренные здесь, но достойные упоминания
Ключевые моменты
- Выражения
import
производят поиск по списку путей вsys.path
. sys.path
всегда включает в себя путь скрипта, запущенного из командной строки, и не зависит от текущей рабочей директории.- Импортирование пакета по сути равноценно импортированию
__init__.py
этого пакета.
Основные определения
- Модуль: любой файл
*.py
. Имя модуля — имя этого файла. - Встроенный модуль: «модуль», который был написан на Си, скомпилирован и встроен в интерпретатор Python, и потому не имеет файла
*.py
. - Пакет: любая папка, которая содержит файл
__init__.py
. Имя пакета — имя папки.С версии Python 3.3 любая папка (даже без __init__.py) считается пакетом. - Объект: в Python почти всё является объектом — функции, классы, переменные и т. д.
Пример структуры директорий
Обратите внимание, что в корневой папке test/
нет файла __init__.py
.
Что делает import
При импорте модуля Python выполняет весь код в нём. При импорте пакета Python выполняет код в файле пакета __init__.py
, если такой имеется. Все объекты, определённые в модуле или __init__.py
, становятся доступны импортирующему.
Основы import и sys.path
Вот как оператор import
производит поиск нужного модуля или пакета согласно документации Python:
При импорте модуля
интерпретатор сначала ищёт встроенный модуль с таким именем. Если такого модуля нет, то идёт поиск файла spam.py
в списке директорий, определённых в переменной sys.path .
sys.path
инициализируется из следующих мест:
- директории, содержащей исходный скрипт (или текущей директории, если файл не указан);
- директории по умолчанию, которая зависит от дистрибутива Python;
PYTHONPATH
(список имён директорий; имеет синтаксис, аналогичный переменной окруженияPATH
).
Программы могут изменять переменную sys.path
после её инициализации. Директория, содержащая запускаемый скрипт, помещается в начало поиска перед путём к стандартной библиотеке. Это значит, что скрипты в этой директории будут импортированы вместо модулей с такими же именами в стандартной библиотеке.
Технически документация не совсем полна. Интерпретатор будет искать не только файл (модуль) spam.py
, но и папку (пакет) spam
.
Обратите внимание, что Python сначала производит поиск среди встроенных модулей — тех, которые встроены непосредственно в интерпретатор. Список встроенных модулей зависит от дистрибутива Python, а найти этот список можно в sys.builtin_module_names
(Python 2 и Python 3). Обычно в дистрибутивах есть модули sys
(всегда включён в дистрибутив), math
, itertools
, time
и прочие.
В отличие от встроенных модулей, которые при поиске проверяются первыми, остальные (не встроенные) модули стандартной библиотеки проверяются после директории запущенного скрипта. Это приводит к сбивающему с толку поведению: возможно «заменить» некоторые, но не все модули стандартной библиотеки. Допустим, модуль math
является встроенным модулем, а random
— нет. Таким образом, import math
в start.py
импортирует модуль из стандартной библиотеки, а не наш файл math.py
из той же директории. В то же время, import random
в start.py
импортирует наш файл random.py
.
Кроме того, импорты в Python регистрозависимы: import Spam
и import spam
— разные вещи.
Функцию pkgutil.iter_modules()
(Python 2 и Python 3) можно использовать, чтобы получить список всех модулей, которые можно импортировать из заданного пути:
Чуть подробнее о sys.path
Чтобы увидеть содержимое sys.path
, запустите этот код:
Документация Python описывает sys.path
так:
Список строк, указывающих пути для поиска модулей. Инициализируется из переменной окружения PYTHONPATH и директории по умолчанию, которая зависит от дистрибутива Python.При запуске программы после инициализации первым элементом этого списка, path[0], будет директория, содержащая скрипт, который был использован для вызова интерпретатора Python. Если директория скрипта недоступна (например, если интерпретатор был вызван в интерактивном режиме или скрипт считывается из стандартного ввода), то path[0] является пустой строкой. Из-за этого Python сначала ищет модули в текущей директории. Обратите внимание, что директория скрипта вставляется перед путями, взятыми из PYTHONPATH.Источник: Python 2 и Python 3
Документация к интерфейсу командной строки Python добавляет информацию о запуске скриптов из командной строки. В частности, при запуске python <script>.py
.
Если имя скрипта ссылается непосредственно на Python-файл, то директория, содержащая этот файл, добавляется в начало sys.path, а файл выполняется как модуль main.Источник: Python 2 и Python 3
Итак, повторим порядок, согласно которому Python ищет импортируемые модули:
- Модули стандартной библиотеки (например,
math
,os
). - Модули или пакеты, указанные в
sys.path
:Если интерпретатор Python запущен в интерактивном режиме:sys.path[0] — пустая строка ”. Это значит, что Python будет искать в текущей рабочей директории, из которой вы запустили интерпретатор. В Unix-системах эту директорию можно узнать с помощью команды pwd.Если мы запускаем скрипт командой python
Обратите внимание, что при запуске скрипта для sys.path
важна не директория, в которой вы находитесь, а путь к самому скрипту. Например, если в командной строке мы находимся в test/folder
и запускаем команду python ./packA/subA/subA1.py
, то sys.path
будет включать в себя test/packA/subA/
, но не test/
.
Кроме того, sys.path
общий для всех импортируемых модулей. Допустим, мы вызвали python start.py
. Пусть start.py
импортирует packA.a1
, а a1.py
выводит на экран sys.path
. В таком случае sys.path
будет включать test/
(путь к start.py
), но не test/packA
(путь к a1.py
). Это значит, что a1.py
может вызвать import other
, так как other.py
находится в test/
.
Всё о __init__.py
У файла __init__.py
есть две функции:
- Превратить папку со скриптами в импортируемый пакет модулей (до Python 3.3).
- Выполнить код инициализации пакета.
Превращение папки со скриптами в импортируемый пакет модулей
Чтобы импортировать модуль (или пакет) из директории, которая находится не в директории нашего скрипта (или не в директории, из которой мы запускаем интерактивный интерпретатор), этот модуль должен быть в пакете.
Как было сказано ранее, любая директория, содержащая файл __init__.py
, является пакетом. Например, при работе с Python 2.7 start.py
может импортировать пакет packA
, но не packB
, так как в директории test/packB/
нет файла __init__.py
.
Это не относится к Python 3.3 и выше благодаря появлению неявных пакетов пространств имён. Проще говоря, в Python 3.3+ все папки считаются пакетами, поэтому пустые файлы __init__.py
больше не нужны.
Допустим, packB
— пакет пространства имён, так как в нём нет __init__.py
. Если запустить интерактивную оболочку Python 3.6 в директории test/
, то мы увидим следующее:
Выполнение кода инициализации пакета
В момент, когда пакет или один из его модулей импортируется в первый раз, Python выполняет __init__.py
в корне пакета, если такой файл существует. Все объекты и функции, определённые в __init__.py
, считаются частью пространства имён пакета.
Рассмотрим следующий пример:
test/packA/a1.py
test/packA/__init__.py
test/start.py
Вывод после запуска python start.py
:
Примечание Если a1.py
вызовет import a2
, и мы запустим python a1.py
, то test/packA/__init__.py
не будет вызван, несмотря на то, что a2
вроде бы является частью пакета packA
. Это связано с тем, что когда Python выполняет скрипт (в данном случае a1.py
), содержащая его папка не считается пакетом.
Использование объектов из импортированного модуля или пакета
Есть 4 разных вида импортов:
import <пакет>
import <модуль>
from <пакет> import <модуль или подпакет или объект>
from <модуль> import <объект>
Пусть X
— имя того, что идёт после import
:
- Если
X
— имя модуля или пакета, то для того, чтобы использовать объекты, определённые вX
, придётся писатьX.объект
. - Если
X
— имя переменной, то её можно использовать напрямую. - Если
X
— имя функции, то её можно вызвать с помощьюX()
.
Опционально после любого выражения import X
можно добавить as Y
. Это переименует X
в Y
в пределах скрипта. Учтите, что имя X
с этого момента становится недействительным. Частым примером такой конструкции является import numpy as np
.
Аргументом для import
может быть как одно имя, так и их список. Каждое из имён можно переименовать с помощью as
. Например, следующее выражение будет действительно в start.py
: import packA as pA, packA.a1, packA.subA.sa1 as sa1
.
Пример: нужно в start.py
импортировать функцию helloWorld()
из sa1.py
.
- Решение 1:
from packA.subA.sa1 import helloWorld
. Мы можем вызвать функцию напрямую по имени:x = helloWorld()
. - Решение 2:
from packA.subA import sa1
или то же самоеimport packA.subA.sa1 as sa1
. Для использования функции нам нужно добавить перед её именем имя модуля:x = sa1.helloWorld()
. Иногда такой подход предпочтительнее первого, так как становится ясно, из какого модуля взялась та или иная функция. - Решение 3:
import packA.subA.sa1
. Для использования функции перед её именем нужно добавить полный путь:x = packA.subA.sa1.helloWorld()
.
Прим. перев. После переименования с помощью as
новое имя нельзя использовать в качестве имени пакета или модуля для последующих импортов. Иными словами, команда вроде следующей недействительна: import packA as pA, pA.a1
.
Используем dir() для исследования содержимого импортированного модуля
После импортирования модуля можно использовать функцию dir()
для получения списка доступных в модуле имён. Допустим, мы импортируем sa1
. Если в sa1.py
есть функция helloWorld()
, то dir(sa1)
будет включать helloWorld
:
Импортирование пакетов
Импортирование пакета по сути равноценно импортированию его __init__.py
. Вот как Python на самом деле видит пакет:
После импорта становятся доступны только те объекты, что определены в __init__.py
пакета. Поскольку в packB
нет такого файла, от import packB
(в Python 3.3.+) будет мало толку, так как никакие объекты из этого пакета не становятся доступны. Последующий вызов модуля packB.b1
приведёт к ошибке, так как он ещё не был импортирован.
Абсолютный и относительный импорт
При абсолютном импорте используется полный путь (от начала корневой папки проекта) к желаемому модулю.
При относительном импорте используется относительный путь (начиная с пути текущего модуля) к желаемому модулю. Есть два типа относительных импортов:
- При явном импорте используется формат
from .<модуль/пакет> import X
, где символы точки.
показывают, на сколько директорий «вверх» нужно подняться. Одна точка.
показывает текущую директорию, две точки..
— на одну директорию выше и т. д. - Неявный относительный импорт пишется так, как если бы текущая директория была частью
sys.path
. Такой тип импортов поддерживается только в Python 2.
В документации Python об относительных импортах в Python 3 написано следующее:
Единственный приемлемый синтаксис для относительных импортов — from .[модуль] import [имя]. Все импорты, которые начинаются не с точки ., считаются абсолютными.Источник: What’s New in Python 3.0
В качестве примера допустим, что мы запускаем start.py
, который импортирует a1
, который импортирует other
, a2
и sa1
. Тогда импорты в a1.py
будут выглядеть следующим образом:
Абсолютные импорты:
Явные относительные импорты:
Неявные относительные импорты (не поддерживаются в Python 3):
Учтите, что в относительных импортах с помощью точек .
можно дойти только до директории, содержащей запущенный из командной строки скрипт (не включительно). Таким образом, from .. import other
не сработает в a1.py
. В результате мы получим ошибку ValueError: attempted relative import beyond top-level package
.
Как правило, абсолютные импорты предпочтительнее относительных. Они позволяют избежать путаницы между явными и неявными импортами. Кроме того, любой скрипт с явными относительными импортами нельзя запустить напрямую:
Имейте в виду, что относительные импорты основаны на имени текущего модуля. Так как имя главного модуля всегда “__main__”, модули, которые должны использоваться как главный модуль приложения, должны всегда использовать абсолютные импорты.Источник: Python 2 и Python 3
Примеры
Пример 1: sys.path известен заранее
Если вы собираетесь вызывать только python start.py
или python other.py
, то прописать импорты всем модулям не составит труда. В данном случае sys.path
всегда будет включать папку test/
. Таким образом, все импорты можно писать относительно этой папки.
Пример: файлу в проекте test
нужно импортировать функцию helloWorld()
из sa1.py
.
Решение: from packA.subA.sa1 import helloWorld
(или любой другой эквивалентный синтаксис импорта).
Пример 2: sys.path мог измениться
Зачастую нам требуется как запускать скрипт напрямую из командной строки, так и импортировать его как модуль в другом скрипте. Как вы увидите далее, здесь могут возникнуть проблемы, особенно в Python 3.
Пример: пусть start.py
нужно импортировать a2
, которому нужно импортировать sa2
. Предположим, что start.py
всегда запускается напрямую, а не импортируется. Также мы хотим иметь возможность запускать a2
напрямую.
Звучит просто, не так ли? Нам всего лишь нужно выполнить два импорта: один в start.py
и другой в a2.py
.
Проблема: это один из тех случаев, когда sys.path
меняется. Когда мы выполняем start.py
, sys.path
содержит test/
, а при выполнении a2.py
sys.path
содержит test/packA/
.
С импортом в start.py
нет никаких проблем. Так как этот модуль всегда запускается напрямую, мы знаем, что при его выполнении в sys.path
всегда будет test/
. Тогда импортировать a2
можно просто с помощью import packA.a2
.
С импортом в a2.py
немного сложнее. Когда мы запускаем start.py
напрямую, sys.path
содержит test/
, поэтому в a2.py
импорт будет выглядеть как from packA.subA import sa2
. Однако если запустить a2.py
напрямую, то в sys.path
уже будет test/packA/
. Теперь импорт вызовет ошибку, так как packA
не является папкой внутри test/packA/
.
Вместо этого мы могли бы попробовать from subA import sa2
. Это решает проблему при запуске a2.py
напрямую, однако теперь создаёт проблему при запуске start.py
. В Python 3 это приведёт к ошибке, потому что subA
не находится в sys.path
(в Python 2 это не вызовет проблемы из-за поддержки неявных относительных импортов).
Обобщим информацию:
Использование относительного импорта from .subA import sa2
будет иметь тот же эффект, что и from packA.subA import sa2
.
Вряд ли для этой проблемы есть чистое решение, поэтому вот несколько обходных путей:
1. Использовать абсолютные импорты относительно директории test/
(т. е. средняя колонка в таблице выше). Это гарантирует, что запуск start.py
напрямую всегда сработает. Чтобы запустить a2.py
напрямую, запустите его как импортируемый модуль, а не как скрипт:
- В консоли смените директорию на
test/
. - Запустите
python -m packA.a2
.
2. Использовать абсолютные импорты относительно директории test/
(средняя колонка в таблице). Это гарантирует, что запуск start.py
напрямую всегда сработает. Чтобы запустить a2.py
напрямую, можно изменить sys.path
в a2.py
, чтобы включить test/packA/
перед импортом sa2
.
Примечание Обычно этот метод работает, однако в некоторых случаях переменная __file__
может быть неправильной. В таком случае нужно использовать встроенный пакет inspect
. Подробнее в этом ответе на StackOverflow.
3. Использовать только Python 2 и неявные относительные импорты (последняя колонка в таблице).
4. Использовать абсолютные импорты относительно директории test/
и добавить её в переменную среды PYTHONPATH
. Это решение не переносимо, поэтому лучше не использовать его. О том, как добавить директорию в PYTHONPATH
, читайте в этом ответе.
Пример 3: sys.path мог измениться (вариант 2)
А вот ещё одна проблема посложнее. Допустим, модуль a2.py
никогда не надо запускать напрямую, но он импортируется start.py
и a1.py
, которые запускаются напрямую.
В этом случае первое решение из примера выше не сработает. Тем не менее, всё ещё можно использовать остальные решения.
Пример 4: импорт из родительской директории
Если мы не изменяем PYTHONPATH
и стараемся не изменять sys.path
программно, то сталкиваемся со следующим основным ограничением импортов в Python: при запуске скрипта напрямую невозможно импортировать что-либо из его родительской директории.
Например, если бы нам пришлось запустить python sa1.py
, то этот модуль не смог бы ничего импортировать из a1.py
без вмешательства в PYTHONPATH
или sys.path
.
На первый взгляд может показаться, что относительные импорты (например from .. import a1
) помогут решить эту проблему. Однако запускаемый скрипт (в данном случае sa1.py
) считается «модулем верхнего уровня». Попытка импортировать что-либо из директории над этим скриптом приведёт к ошибке ValueError: attempted relative import beyond top-level package
.
Для решения этой проблемы лучше её не создавать и избегать написания скриптов, которые импортируют из родительской директории. Если этого нельзя избежать, то предпочтительным обходным путём является изменение sys.path
.
Python 2 vs Python 3
Мы разобрали основные отличия импортов в Python 2 и Python 3. Они ещё раз изложены здесь наряду с менее важными отличиями:
- Python 2 поддерживает неявные относительные импорты, Python 3 — нет.
- В Python 2, чтобы папка считалась пакетом и её можно было импортировать, она должна содержать файл
__init__.py
. С версии Python 3.3 благодаря введению неявных пакетов пространств имён все папки считаются пакетами вне зависимости от наличия__init__.py
. - В Python 2 можно написать
from <модуль> import *
внутри функции, а в Python 3 — только на уровне модуля.
Ещё немного полезной информации по импортам
- Можно использовать переменную
__all__
в__init__.py
, чтобы указать, что будет импортировано выражениемfrom <модуль> import *
. Смотрите документацию для Python 2 и Python 3. - Можно использовать
if __name__ == '__main__'
для проверки, был ли скрипт импортирован или запущен напрямую. Документация для Python 2 и Python 3. - Можно установить проект в качестве пакета (в режиме разработчика) с помощью
pip install -e <проект>
, чтобы добавить корень проекта вsys.path
. Подробнее в этом ответе на StackOverflow. from <модуль> import *
не импортирует имена из модуля, которые начинаются с нижнего подчеркивания_
. Подробнее читайте в документации Python 2 и Python 3.
282К открытий296К показов