Инициализирование Python-пакетов: роль и возможности __init__.py

Раздел: Разработка на Python -> Создание пакетов

Инициализация пакета Python: файл __init__.py

Файл __init__.py - это обязательный элемент пакета Python (в версиях до 3.3) или опциональный (начиная с PEP 420, пространства имён). Он определяет, что каталог является пакетом, и может содержать код инициализации, который выполняется при первом импорте пакета. Основное назначение - сделать каталог импортируемым как модуль. Минимальная реализация - пустой файл __init__.py, который просто маркирует пакет.

# mypackage/__init__.py
# Пустой файл - пакет существует, но не предоставляет дополнительной логики.

Python package init (инициализация пакета python (__init__.py))

После этого любой код может выполнить import mypackage или from mypackage import something.

Как сделать доступными модули пакета при импорте пакета?

Часто в __init__.py импортируют подмодули, чтобы они были доступны как атрибуты пакета. Это удобно для пользователя.

# mypackage/__init__.py
from . import module1
from . import module2

Python create package (создание пакета в python)

# main.py
import mypackage
print(mypackage.module1)  # доступен без дополнительного импорта

Проблема: если модуль1 импортирует что-то из модуля2, а модуль2 в свою очередь из модуля1, возникает циклический импорт. Решение - импортировать только после определения функций, либо использовать локальные импорты внутри функций.

Как определить переменные или функции, доступные как атрибуты пакета?

Можно объявить переменные и функции прямо в __init__.py, и они станут частью пространства имён пакета.

# mypackage/__init__.py
__version__ = '2.0.0'
def greet(name):
    return f"Hello, {name}"
# main.py
import mypackage
print(mypackage.__version__)
print(mypackage.greet('World'))

Проблема: излишняя инициализация может замедлить импорт, если пакет большой. Рекомендуется выносить тяжёлую логику в отдельные модули.

Как ограничить список импортируемых имён при использовании from package import *?

Задание списка __all__ в __init__.py контролирует, какие имена будут импортированы оператором from package import *.

# mypackage/__init__.py
__all__ = ['public_func', 'PublicClass']
from .private import _internal_function
from .public import public_func, PublicClass
# main.py
from mypackage import *
# _internal_function не будет импортирована
print(public_func)  # доступно

Типичная ошибка: забыть обновить __all__ после добавления новых модулей. Это приводит к тому, что from package import * не включает нужные имена. Решение - регулярно проверять список.

Как выполнить код при первом импорте пакета?

Любой код верхнего уровня в __init__.py выполняется однократно при первом импорте пакета. Это можно использовать для проверки зависимостей или конфигурации.

# mypackage/__init__.py
import sys
if sys.version_info < (3, 8):
    raise RuntimeError('Требуется Python 3.8+')
# Создание глобального логгера
import logging
logger = logging.getLogger(__name__)
logger.info('Пакет инициализирован')

Проблема: побочные эффекты, если пакет импортируется из нескольких мест. Решение - использовать ленивые инициализации или синглтоны.

Как управлять импортом подпакетов для ускорения загрузки?

Можно отложить импорт тяжёлых подмодулей до момента, когда они реально понадобятся, используя функцию внутри __init__.py или условный импорт.

# mypackage/__init__.py
def get_data_processor():
    from . import data_processor
    return data_processor
# main.py
import mypackage
# data_processor пока не импортирован
proc = mypackage.get_data_processor()  # теперь импортируется

Распространённая ошибка: случайный импорт из __init__.py всех подмодулей, что замедляет импорт. Рекомендуется импортировать только то, что действительно нужно сразу.

Расширенные примеры использования __init__.py

Пример 1: Автоматическая регистрация модулей в реестре

Пакет с подключаемыми плагинами. __init__.py обходит подмодули и добавляет их в словарь plugins.

Пример
# mypackage/__init__.py
import pkgutil
import importlib

plugins = {}

# Обход всех модулей в пакете (кроме __init__)
for loader, module_name, is_pkg in pkgutil.iter_modules(__path__):
    if is_pkg:
        continue
    module = importlib.import_module('.' + module_name, package=__name__)
    if hasattr(module, 'register'):
        plugins[module_name] = module.register()
Пример
# mypackage/plugin_a.py
def register():
    return {'name': 'A', 'version': 1}
# Результат
# import mypackage
# print(mypackage.plugins)
# {'plugin_a': {'name': 'A', 'version': 1}}

Пример 2: Использование __all__ с вложенными пакетами

Пакет mypackage содержит подпакет sub. В __init__.py корневого пакета указываем __all__ для подпакета.

Пример
# mypackage/__init__.py
from . import sub

__all__ = ['sub', 'public_function']

def public_function():
    return 'ok'
Пример
# mypackage/sub/__init__.py
__all__ = ['SubClass']

class SubClass:
    pass
Пример
# main.py
from mypackage import *
# Доступны: sub и public_function
print(sub)  # <module 'mypackage.sub'>
print(public_function())  # ok
# SubClass не доступна напрямую, требуется mypackage.sub.SubClass

Пример 3: Динамическая загрузка языковых файлов

Пакет поддерживает локализацию. В __init__.py считывается настройка языка и загружается соответствующий модуль.

Пример
# mypackage/__init__.py
import os
import importlib

LANG = os.environ.get('MYAPP_LANG', 'en')

try:
    _ = importlib.import_module(f'src.lang.{LANG}')
    tr = _.translations
except ImportError:
    # fallback на английский
    from . import en as lang_module
    tr = lang_module.translations
Пример
# mypackage/en.py
translations = {
    'hello': 'Hello'
}
Пример
# mypackage/ru.py
translations = {
    'hello': 'Привет'
}
# Результат при MYAPP_LANG=ru
# import mypackage
# print(mypackage.tr['hello'])  # 'Привет'

Пример 4: Отложенный импорт с помощью собственного менеджера

Класс-контейнер, который импортирует модули только при обращении к атрибуту.

Пример
# mypackage/__init__.py
import sys
import types

class LazyModule(types.ModuleType):
    def __getattr__(self, name):
        try:
            return super().__getattribute__(name)
        except AttributeError:
            # Импорт модуля по требованию
            module = importlib.import_module(f'.{name}', package=__name__)
            setattr(self, name, module)
            return module

sys.modules[__name__].__class__ = LazyModule
Пример
# main.py
import mypackage
# Модуль utils ещё не импортирован
print('utils' in sys.modules)  # False
mypackage.utils  # теперь импортируется
print('utils' in sys.modules)  # True

Пример 5: Подготовка данных пакета с помощью importlib.resources

В __init__.py можно заранее загрузить статические файлы пакета и сделать их доступными.

Пример
# mypackage/__init__.py
import importlib.resources as pkg_resources

# Загрузить содержимое файла data/config.json
_config_text = None
def get_config():
    global _config_text
    if _config_text is None:
        _config_text = pkg_resources.read_text('mypackage.data', 'config.json')
    return _config_text
Пример
# mypackage/data/config.json
{"key": "value"}
Пример
# main.py
import mypackage
print(mypackage.get_config())  # выведет содержимое JSON

Инициализация пакета Python (__init__.py) - comments

En
Python package init (python)