Инициализация пакета Python через __init__.py

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

Основные принципы работы __init__.py

Файл __init__.py автоматически выполняется при импорте пакета. В Python 3.3+ он может отсутствовать, если используется namespace packages, но для обычных пакетов его наличие обязательно. Основное назначение - инициализация пакета и управление импортами.


# mypackage/__init__.py
from .submodule import useful_class
from . import utils

__all__ = ['useful_class', 'utils']

Python init script (инициализация скрипта python (__init__.py))

Этот код делает useful_class и utils доступными напрямую через import mypackage. Без __init__.py пакет не будет распознан интерпретатором (в старых версиях Python).

Типичные проблемы:

  • Циклические импорты - когда подмодули импортируют друг друга через пакет. Решение: отложенные импорты внутри функций или использование абсолютных путей.
  • Побочные эффекты при импорте - если __init__.py выполняет тяжелую инициализацию (запись в БД, загрузку файлов), это замедляет импорт. Решение: перенести такую логику в отдельную функцию, вызываемую явно.

Как определить список экспортируемых имен через __all__?

__all__ задает, какие имена будут импортированы при from package import *. Если __all__ не указан, импортируются все имена, не начинающиеся с подчеркивания. Это помогает контролировать публичный интерфейс.


# __init__.py
from .module_a import A
from ._private import _internal

__all__ = ['A']  # _internal не будет экспортирован

Ошибка: забыли указать __all__ - пользователи могут случайно импортировать внутренние модули. Решение: всегда явно определять __all__ для крупных пакетов.

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

Например, настроить логирование или считать конфигурацию из переменных окружения. Главное - избегать блокирующих операций.


# __init__.py
import logging
import os

logging.basicConfig(level=os.getenv('LOG_LEVEL', 'INFO'))
logger = logging.getLogger(__name__)
logger.info('Package initialized')

Проблема: двойная инициализация при импорте в нескольких местах. Решение: проверять флаг или использовать синглтон.

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

Если подмодули тяжелые, их импорт можно отложить до первого обращения. В Python 3.7+ для этого используется __getattr__ на уровне пакета.


# __init__.py
import importlib

def __getattr__(name):
    if name == 'heavy_module':
        return importlib.import_module(f'{__name__}.heavy_module')
    raise AttributeError(f'module {__name__!r} has no attribute {name!r}')

Теперь mypackage.heavy_module импортируется только при обращении к нему.

Ошибка: __getattr__ срабатывает для каждого обращения. Если имя совпадает с уже существующим атрибутом, функция не вызывается. Решение: проверять, что имя отсутствует в sys.modules.

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

Для автоматизации импорта всех файлов в пакете можно использовать pkgutil или importlib. Полезно для плагинов или динамической регистрации.


# __init__.py
import pkgutil
import importlib

__all__ = []
for loader, module_name, is_pkg in pkgutil.walk_packages(__path__):
    if not is_pkg:
        module = importlib.import_module(f'{__name__}.{module_name}')
        globals()[module_name] = module
        __all__.append(module_name)

Проблема: порядок импорта может вызвать циклические зависимости. Решение: явно сортировать модули или использовать topological_sort.

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

Пример 1: Реализация lazy-импорта с помощью __getattr__

Создадим пакет mypackage с тяжелыми модулями. Вместо того чтобы импортировать их все сразу, будем загружать по требованию.

Пример

# mypackage/__init__.py
import importlib
import sys

# Список ленивых модулей
_LAZY_MODULES = ['heavy1', 'heavy2', 'heavy3']

def __getattr__(name):
    if name in _LAZY_MODULES:
        module = importlib.import_module(f'{__name__}.{name}')
        # Кэшируем модуль в sys.modules, чтобы повторный вызов не триггерил __getattr__
        sys.modules[f'{__name__}.{name}'] = module
        return module
    raise AttributeError(f'Модуль {__name__!r} не содержит {name!r}')

Результат:

>>> import mypackage
>>> # heavy1, heavy2, heavy3 еще не импортированы
>>> mypackage.heavy1  # теперь импортируется heavy1

Пример 2: Автоматическая регистрация плагинов

В пакете plugins каждый файл содержит класс с атрибутом name. В __init__.py собираем их в словарь.

Пример

# plugins/__init__.py
import pkgutil
import importlib

plugins = {}

for loader, module_name, is_pkg in pkgutil.iter_modules(__path__):
    module = importlib.import_module(f'{__name__}.{module_name}')
    for attr_name in dir(module):
        attr = getattr(module, attr_name)
        if hasattr(attr, 'name'):
            plugins[attr.name] = attr

Пример модуля плагина:

Пример

# plugins/plugin_a.py
class PluginA:
    name = 'A'
    def run(self):
        return 'Plugin A executed'

Результат:

>>> from plugins import plugins
>>> plugins['A'].run()
'Plugin A executed'

Пример 3: Инициализация с конфигурацией из переменных окружения

Пакет config при импорте загружает настройки из .env или переменных системы.

Пример

# config/__init__.py
import os
from dotenv import load_dotenv

load_dotenv()  # загружаем .env если есть

DATABASE_URL = os.getenv('DATABASE_URL', 'sqlite:///default.db')
SECRET_KEY = os.getenv('SECRET_KEY')
if not SECRET_KEY:
    raise RuntimeError('SECRET_KEY не задан')

Использование:

>>> from config import DATABASE_URL, SECRET_KEY
>>> print(DATABASE_URL)
postgresql://user:pass@localhost/db

Пример 4: Динамическое создание публичного API

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

Пример

# api/__init__.py
import pkgutil
import importlib

# Собираем все функции, имя которых не начинается с подчеркивания
_public_functions = {}

for loader, name, is_pkg in pkgutil.iter_modules(__path__):
    module = importlib.import_module(f'{__name__}.{name}')
    for func_name in dir(module):
        if not func_name.startswith('_'):
            func = getattr(module, func_name)
            if callable(func):
                _public_functions[func_name] = func

# Экспортируем все функции через globals()
globals().update(_public_functions)
__all__ = list(_public_functions.keys())

Результат:

>>> from api import my_func
>>> my_func()
'Executed'

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

En
Python init script (python)