Никита Прияцелюк

Как работают импорты в Python

Импорты не так просты, как кажется. Тем более если они пишутся для двух несовместимых версий языка. Разбираемся с основными проблемами и их решением.

261720
Обложка поста Как работают импорты в Python

Порой бывает трудно правильно реализовать import с первого раза, особенно если мы хотим добиться правильной работы на плохо совместимых между собой версиях Python 2 и Python 3. Попытаемся разобраться, что из себя представляют импорты в Python и как написать решение, которое подойдёт под обе версии языка.

Содержание

Ключевые моменты

  • Выражения import производят поиск по списку путей в sys.path.
  • sys.path всегда включает в себя путь скрипта, запущенного из командной строки, и не зависит от текущей рабочей директории.
  • Импортирование пакета по сути равноценно импортированию  __init__.py этого пакета.

Основные определения

  • Модуль: любой файл *.py. Имя модуля — имя этого файла.
  • Встроенный модуль: «модуль», который был написан на Си, скомпилирован и встроен в интерпретатор Python, и потому не имеет файла *.py.
  • Пакет: любая папка, которая содержит файл __init__.py. Имя пакета — имя папки.С версии Python 3.3 любая папка (даже без __init__.py) считается пакетом.
  • Объект: в Python почти всё является объектом — функции, классы, переменные и т. д.

Пример структуры директорий

			test/                      # Корневая папка
    packA/                 # Пакет packA
        subA/              # Подпакет subA
            __init__.py
            sa1.py
            sa2.py
        __init__.py
        a1.py
        a2.py
    packB/                 # Пакет packB (неявный пакет пространства имён)
        b1.py
        b2.py
    math.py
    random.py
    other.py
    start.py
		

Обратите внимание, что в корневой папке test/ нет файла __init__.py.

Что делает import

При импорте модуля Python выполняет весь код в нём. При импорте пакета Python выполняет код в файле пакета __init__.py, если такой имеется. Все объекты, определённые в модуле или __init__.py, становятся доступны импортирующему.

Основы import и sys.path

Вот как оператор import производит поиск нужного модуля или пакета согласно документации Python:

При импорте модуля spam интерпретатор сначала ищёт встроенный модуль с таким именем. Если такого модуля нет, то идёт поиск файла spam.py в списке директорий, определённых в переменной sys.path. sys.path инициализируется из следующих мест:
    t
  • директории, содержащей исходный скрипт (или текущей директории, если файл не указан);
  • t
  • директории по умолчанию, которая зависит от дистрибутива Python;
  • t
  • PYTHONPATH (список имён директорий; имеет синтаксис, аналогичный переменной окружения PATH).
Программы могут изменять переменную sys.path после её инициализации. Директория, содержащая запускаемый скрипт, помещается в начало поиска перед путём к стандартной библиотеке. Это значит, что скрипты в этой директории будут импортированы вместо модулей с такими же именами в стандартной библиотеке.Источник: Python 2 и Python 3

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

			import pkgutil
search_path = ['.'] # Используйте None, чтобы увидеть все модули, импортируемые из sys.path
all_modules = [x[1] for x in pkgutil.iter_modules(path=search_path)]
print(all_modules)
		

Чуть подробнее о sys.path

Чтобы увидеть содержимое sys.path, запустите этот код:

			import sys
print(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 ищет импортируемые модули:

  1. Модули стандартной библиотеки (например, math, os).
  2. Модули или пакеты, указанные в sys.path:Если интерпретатор Python запущен в интерактивном режиме:sys.path[0] — пустая строка ''. Это значит, что Python будет искать в текущей рабочей директории, из которой вы запустили интерпретатор. В Unix-системах эту директорию можно узнать с помощью команды pwd.Если мы запускаем скрипт командой python