Инициализирование Python-пакетов: роль и возможности __init__.py
Инициализация пакета 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 module2Python 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