Python __init__.py, больше чем просто пустой файл



Многие разработчики Python используют файл __init__.py как пустой файл для «распознавания этой директории как пакета». Однако этот файл не просто маркер.

Суть __init__.py заключается в том, что это «скрипт, который выполняется первым при импортировании пакета».

Используя эту особенность, можно выполнить очень мощные задачи, такие как очистка неаккуратного кода, ясное проектирование API пакета и управление логикой инициализации.


1. Упрощение неаккуратных импортов (Упрощение API)

Это самый полезный и общий способ использования __init__.py. Он «поднимает» классы или функции подпакетов на уровень пакета, чтобы пользователи могли легче их импортировать.

Проблема:

Предположим, что views.py в Django содержит больше 1000 строк, и файлы разделены по функциям.

payments/
├── views/
│   ├── __init__.py
│   ├── user_views.py    # UserProfileView и др.
│   ├── payment_views.py # PaymentView и др.
│   └── webhooks.py      # PaypalWebhookView и др.
└── urls.py

Использовать эти виды в urls.py может быть очень хлопотно.

# payments/urls.py (плохой пример)
from .views import user_views, payment_views, webhooks

urlpatterns = [
    path('profile/', user_views.UserProfileView.as_view()),
    path('toss/', payment_views.PaymentView.as_view()),
    path('webhook/', webhooks.PaypalWebhookView.as_view()),
]

Теперь urls.py должен знать всю внутреннюю структуру views.


Решение (Использование init.py):

Импортируем необходимые виды заранее в файл views/init.py.

# payments/views/__init__.py

# Поднимаем необходимые классы/функции на уровень пакета.
from .user_views import UserProfileView
from .payment_views import PaymentView
from .webhooks import PaypalWebhookView

Теперь urls.py, использующий пакет views, выглядит так, будто views непосредственно содержит UserProfileView.

# payments/urls.py (улучшенный пример)

# 'views' теперь это пакет (директория), а не файл.
from . import views

urlpatterns = [
    # Благодаря __init__.py теперь не нужно знать внутреннюю структуру.
    path('profile/', views.UserProfileView.as_view()),
    path('toss/', views.PaymentView.as_view()),
    path('webhook/', views.PaypalWebhookView.as_view()),
]

Код urls.py остается неизменным, независимо от того, является ли views.py файлом или директорией с несколькими файлами. Это хорошая абстракция.


2. Явное указание 'официального API' пакета (__all__)



from my_package import * (импорт с подстановочным знаком) удобен, но может привести к тому, что непреднамеренные переменные или модули (например, import os) попадут в текущее пространство имен и загрязнят код.

__all__ определяет разрешенный список, указывая, «брали только то, что есть в этом списке», когда используется import *.

# payments/views/__init__.py

# Модули для внутреннего использования
import logging 
from .internal_utils import _helper_function

# Открытые виды
from .user_views import UserProfileView
from .payment_views import PaymentView

# Определение __all__:
# "при использовании import * можно будет взять только UserProfileView и PaymentView."
__all__ = [
    'UserProfileView',
    'PaymentView'
]

Теперь, если выполнить from payments.views import *, модуль logging или внутренняя функция _helper_function не будут импортированы, что позволяет безопасно защитить пространство имен.


3. Инициализация пакета и определение метаданных

Это идеальное место для размещения кода, который должен выполняться всего один раз при загрузке пакета.

  • Предоставление информации о версии:
# my_library/__init__.py
__version__ = "1.2.0"
__author__ = "whitedec"

Теперь пользователь может легко проверить версию, выполнив import my_library; print(my_library.__version__).

  • Выполнение глобальных настроек:

Представьте, что Django вызывает django.setup() при импортировании django, или что код, инициализирующий Celery, включен в init.py. Это типичный пример таких случаев.


# my_project/__init__.py (пример с Celery)

# Обеспечение загрузки Celery вместе с Django
from .celery import app as celery_app

__all__ = ('celery_app',)


※ Предостережение: Циклические ссылки между внутренними модулями

Существуют некоторые вещи, на которые следует обратить внимание, когда вы используете __init__.py как «информационный стол».

Предположим, что views/webhooks.py должен использовать CancelView из views/payment_commons.py.

  • Плохой способ (риск циклической ссылки):

Попытка импортировать CancelView через from . import CancelView из webhooks.py (т.е. через init.py).

  • Хороший способ (прямой импорт):

Импортировать непосредственно из необходимых модулей, например from .payment_commons import CancelView в webhooks.py.

Правило:

  • Внешние пользователи (например, urls.py): используют API через 'информационный стол' (__init__.py).
  • Внутренние модули (например, webhooks.py): импортируют функционал напрямую из соседних модулей (payment_commons.py) без прохода через 'информационный стол'.

Заключение

__init__.py — это не просто пустой файл, это мощный инструмент, выполняющий роль 'информационного стола', 'официальной документации API' и 'скрипта начальной настройки' пакета. При правильном использовании этого файла можно сделать структуру проекта намного более аккуратной и удобной для поддержки.