Файл __init__.py: зачем он нужен и как его использовать
Файл __init__.py является важным элементом структуры проекта на Python. Его наличие или отсутствие определяет, как интерпретатор обрабатывает директорию с модулями. В этой статье рассматриваются практические случаи использования файла __init__.py, варианты его содержимого и связанные с ним ошибки.
Для чего нужен файл __init__.py
Файл __init__.py помещается внутри директории пакета. Он может быть пустым, но может содержать и исполняемый код. От содержимого зависит, какие объекты станут доступны при импорте пакета.
Как сделать папку с модулями пакетом Python?
Основное решение состоит в создании пустого файла __init__.py внутри папки. Такой файл указывает интерпретатору Python, что данную директорию следует рассматривать как пакет. Структура каталога выглядит так:
my_package/
__init__.py
module_a.py
module_b.pyPython init py (файл __init__.py в python)
В модуле module_a.py находится функция:
# module_a.py
def hello():
return "Привет из module_a"
Аналогично для module_b.py:
# module_b.py
def world():
return "мир"
Теперь импорт работает:
from my_package import module_a
print(module_a.hello())
Привет из module_a
Пустой __init__.py является минимально достаточным решением. Он не добавляет лишней логики и не вызывает побочных эффектов при импорте.
Как сделать импорт из пакета более коротким?
Если в __init__.py указать имена из вложенных модулей, то импорт можно выполнять напрямую из пакета. Например, содержимое __init__.py:
# my_package/__init__.py
from .module_a import hello
from .module_b import world
После этого можно обращаться к функциям без указания имени модуля:
from my_package import hello, world
print(hello(), world())
Привет из module_a мир
Такой подход удобен для формирования общедоступного API пакета.
Как ограничить импорт с помощью from package import *?
Специальная переменная __all__ в __init__.py определяет список имён, которые будут импортированы при использовании звёздочки. Пример:
# my_package/__init__.py
__all__ = ["hello", "world"]
Теперь команда from my_package import * импортирует только указанные имена, даже если в пакете есть другие модули.
Как выполнить инициализацию при импорте пакета?
Код в __init__.py выполняется один раз при первом импорте пакета. Это можно использовать для настройки логгера, чтения конфигурационного файла или инициализации базы данных. Пример:
# my_package/__init__.py
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
При импорте пакета logger будет доступен как my_package.logger.
Что делать, чтобы пакет мог существовать без __init__.py?
В Python 3.3 и новее поддерживаются namespace-пакеты. Такая директория не содержит __init__.py и может быть разделена на несколько частей. Это вариант для случая, когда пакет распространяется как несколько отдельных компонентов. Однако для обычного пакета с явным содержимым рекомендуется использовать __init__.py.
Типичные ошибки при работе с __init__.py
Ошибка 1. Забыли создать __init__.py. В Python 2 это приведёт к ошибке импорта. В Python 3 директория без __init__.py становится namespace-пакетом, что может вызвать неопределённость при импорте. Способ решения: добавить пустой файл __init__.py.
Ошибка 2. Побочные эффекты при импорте. Код в __init__.py выполняется при первом импорте. Если он изменяет глобальное состояние, это влияет на всю программу. Способ решения: размещать такие операции в отдельном модуле и вызывать их явно.
Ошибка 3. Круговая зависимость. Если __init__.py импортирует модуль, а этот модуль пытается импортировать пакет, возникает конфликт. Способ решения: перенести импорт внутрь функции или пересмотреть структуру пакета.
Расширенные примеры использования __init__.py
Ниже описаны нестандартные способы применения файла __init__.py в реальных проектах.
Как динамически импортировать все модули пакета?
Для этого применяется функция pkgutil.iter_modules вместе с importlib.import_module, чтобы перечислить все модули внутри пакета и импортировать их автоматически.
# пакет my_package/__init__.py
import importlib
import pkgutil
__all__ = []
for module_info in pkgutil.iter_modules(__path__):
module_name = module_info.name
module = importlib.import_module(f"{__name__}.{module_name}")
__all__.append(module_name)
Эта инструкция полезна для плагинных архитектур, когда заранее неизвестен состав пакета.
# my_package/module_a.py
def a():
return "A"
# my_package/module_b.py
def b():
return "B"
import my_package
print(my_package.module_a.a())
print(my_package.module_b.b())
A B
Как реализовать ленивую загрузку подмодулей?
PEP 562 позволяет определить функцию __getattr__ на уровне модуля. Это сокращает время импорта, так как подмодули загружаются только при обращении к ним.
# my_package/__init__.py
import importlib
__all__ = ["module_a", "module_b"]
def __getattr__(name):
if name in __all__:
return importlib.import_module(f"{__name__}.{name}")
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
def __dir__():
return __all__
Теперь можно писать:
import my_package
print(my_package.module_a.hello())
Привет из module_a
Как определить метаданные пакета в __init__.py?
# my_package/__init__.py
__version__ = "1.2.0"
__author__ = "Иван Иванов"
__all__ = ["version_info"]
version_info = (1, 2, 0)
Эти данные удобно использовать при сборке и документировании проекта:
import my_package
print(my_package.__version__)
1.2.0
Как создать общее состояние для всех модулей пакета?
# my_package/__init__.py
config = {"debug": False}
def set_debug(value):
config["debug"] = value
Модули пакета могут импортировать config из __init__.py:
# my_package/module_a.py
from my_package import config
def info():
return f"debug: {config['debug']}"
import my_package
my_package.set_debug(True)
from my_package.module_a import info
print(info())
debug: True