Пакеты в Python
Пакеты в Python: как организовать код в директории с __init__.py, импортировать модули и использовать пространства имён.
Пакет — это директория, содержащая файл
__init__.pyи один или несколько модулей (файлов.py). Пакеты позволяют группировать связанный код и избегать конфликтов имён.
Зачем нужны пакеты
Когда проект разрастается до десятков файлов, держать их в одной директории неудобно: сложно искать нужный модуль и легко получить конфликт имён. Пакеты решают обе проблемы — как папки на рабочем столе, только для кода.
Структура пакета
Предположим, вы разрабатываете игру. Вот типичная структура пакетов:
# Структура директорий:
# game/
# __init__.py ← делает папку пакетом
# settings.py
# level/
# __init__.py
# start.py
# finish.py
# player/
# __init__.py
# hero.py
# enemy.py
Файл __init__.py может быть пустым — само его присутствие сообщает Python: «эта папка — пакет». При желании в него помещают инициализационный код: импорты, константы, версию пакета.
Три способа импортировать из пакета
Способ 1: импорт целого модуля через точку
import game.level.start
# Обращаемся через полный путь
game.level.start.select_difficulty(2)
Явно, но длинно. Подходит, когда важно видеть, откуда пришла функция.
Способ 2: импорт модуля с псевдонимом
from game.level import start
# Обращаемся только через имя модуля
start.select_difficulty(2)
Короче, пространство имён сохраняется.
Способ 3: прямой импорт объекта
from game.level.start import select_difficulty
# Вызываем напрямую
select_difficulty(2)
Самый лаконичный, но опасный при конфликтах имён. Если в другом модуле тоже есть select_difficulty, последний импорт перезапишет предыдущий.
Правило: чем длиннее путь при импорте, тем безопаснее — конфликт имён исключён. Используйте способ 3 только для часто вызываемых функций из хорошо известных модулей.
Файл __init__.py: что в него кладут
# game/__init__.py
VERSION = "1.0.0"
# Реэкспорт часто используемых объектов, чтобы пользователи
# пакета могли писать: from game import Hero
from game.player.hero import Hero
После этого пользователь пишет коротко:
from game import Hero # работает, хотя Hero определён глубоко внутри
print(Hero)
Вывод (при наличии класса Hero):
<class 'game.player.hero.Hero'>
Относительные импорты
Внутри пакета модули могут импортировать друг друга относительно текущего местоположения:
# game/level/finish.py
from . import start # . = текущий пакет (game/level)
from ..player import hero # .. = родительский пакет (game), затем player
Точка означает «текущий пакет», две точки — «родительский». Относительные импорты работают только внутри пакета, в скриптах верхнего уровня их использовать нельзя.
Как Python ищет пакеты: sys.path
import sys
# Python ищет пакеты и модули в этих директориях по порядку
for path in sys.path:
print(path)
Вывод (пример):
/Users/user/project /usr/lib/python3.12 /usr/lib/python3.12/lib-dynload /usr/local/lib/python3.12/site-packages
Первой стоит директория текущего скрипта. Дальше — стандартная библиотека и site-packages (туда pip устанавливает сторонние пакеты).
Сторонние пакеты
Установка через pip и импорт работают так же, как для собственных пакетов:
# В терминале: pip install requests
import requests # теперь пакет доступен
response = requests.get("https://httpbin.org/get")
print(response.status_code)
Вывод:
200
Частые ошибки
| Ошибка | Причина | Решение |
ModuleNotFoundError | директория не в sys.path или нет __init__.py | проверьте структуру, добавьте __init__.py |
конфликт имён при from x import * | перезапись глобальных имён | используйте явные импорты |
| циклический импорт | модуль A импортирует B, B импортирует A | вынесите общий код в третий модуль |
Коротко
- Пакет — директория с файлом
__init__.py; он может быть пустым. - Три способа импорта: через точку,
from pkg import module,from pkg.module import obj. - Чем полнее путь импорта, тем меньше риск конфликта имён.
- Python ищет пакеты по
sys.path;pipустанавливает вsite-packages. - Относительные импорты (
from . import x) — только внутри пакета.