Файл __init__.py: зачем он нужен и как его использовать

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

Файл __init__.py является важным элементом структуры проекта на Python. Его наличие или отсутствие определяет, как интерпретатор обрабатывает директорию с модулями. В этой статье рассматриваются практические случаи использования файла __init__.py, варианты его содержимого и связанные с ним ошибки.

Для чего нужен файл __init__.py

Файл __init__.py помещается внутри директории пакета. Он может быть пустым, но может содержать и исполняемый код. От содержимого зависит, какие объекты станут доступны при импорте пакета.

Как сделать папку с модулями пакетом Python?

Основное решение состоит в создании пустого файла __init__.py внутри папки. Такой файл указывает интерпретатору Python, что данную директорию следует рассматривать как пакет. Структура каталога выглядит так:

my_package/
    __init__.py
    module_a.py
    module_b.py

Python 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

файл __init__.py в Python - comments

En
Python init py (python)