June 8, 2020

Работа с пакетами в Python

Пакеты

Мы уже знаем, что в Python код хранится в отдельных файлах, называемых модулями. Но если начать делить код достаточно большого проекта на модули, то довольно быстро может возникнуть желание сгруппировать несколько модулей "по смыслу". Или же мы захотим вынести часть модулей из проекта с целью их использования в других проектах. Для объединения модулей в группы служат пакеты (packages).

Итак, пакет — это директория (далее "каталог") с файлами модулей, имеющая имя в формате "snake_case" и содержащая, помимо прочего, специальный модуль с именем "__init__.py". Именно наличие этого специального файла подсказывает интерпретатору Python, что каталог следует воспринимать именно как пакет.

Простейший пакет

Давайте рассмотрим пример простейшего пакета. Пусть пакет состоит из каталога package и модуля __init__.py внутри этого каталога:

package/
└── __init__.py

Файл __init__.py пусть содержит код:

# file __init__.py
NAME = 'super_package'

Это, хотя и небольшой, но уже полноценный пакет. Его можно импортировать так же, как мы импортировали бы модуль:

import package

print(package.NAME)

Заметьте — мы не импортировали файл __init__.py непосредственно. При первом обращении к пакету Python автоматически импортирует модуль __init__.py в этом пакете. Поэтому, очевидно, нельзя импортировать "просто каталог" — ведь каталог без файла __init__.py не будет полноценным пакетом!

Содержимое пакета

С простым пакетом всё ясно — его можно использовать как модуль. Но давайте уже перейдём к группировке в пакете нескольких модулей! Для этого в пакет положим ещё два модуля:

package/
├── constants.py
├── functions.py
└── __init__.py

Содержимое модуля constants.py:

# file constants.py
PERSON = 'Alice'

Содержимое модуля functions.py:

# file functions.py
def greet(who):
    print('Hello, ' + who + '!')

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

import package.functions
import package.constants

package.functions.greet(package.constants.PERSON)  # => Hello, Alice!

Этот вариант самый понятный: в строчке вызова функции greet сразу видно, откуда пришла функция, а откуда — её аргумент. Но писать имя пакета и имя модуля каждый раз — утомительно! Давайте импортируем саму функцию и аргумент:

from package.functions import greet
from package.constants import PERSON

greet(PERSON)  # => Hello, Alice!

Так строчка вызова функции выглядит гораздо лучше! Но помните, что тому, кто будет читать этот код в дальнейшем, потребуется посмотреть в блок импортов, чтобы узнать, откуда функция и константа появились.

Пакеты и импорты

Импорты в Python бывают двух видов: абсолютные и относительные. Понимание разницы становится особенно важным, когда вы начинаете работать с пакетами.

Абсолютные импорты

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

Относительные импорты

Относительные импорты выглядят так:

from . import module
from .module import function
from .subpackage.module import CONSTANT

Здесь точка означает текущую директорию для модуля, который и осуществляет импорт. К примеру, импорт из .module означают импорт из модуля, находящегося в той же директории, которая содержит модуль с данным импортом. Звучит сложновато? Так и есть! Авторы языка тоже так считают, поэтому уже сейчас относительные импорты лишены части возможностей, которые были доступны в более ранних версиях языка (да, раньше было ещё сложнее!).

Какие же импорты использовать?

Я придерживаюсь мнения большинства: нужно всегда использовать абсолютные импорты! Даже если вы внутри одного пакета один модуль импортируете в другой. Да, при использовании абсолютных импортов приходится писать чуть больше. Но понятность кода — слишком полезное свойство, чтобы от него отказываться!l