Python __init__.py, meer dan een leeg bestand



Veel Python-ontwikkelaars gebruiken het __init__.py-bestand als een leeg bestand om "deze directory als een pakket te herkennen". Maar dit bestand is niet gewoon een marker.

De essentie van __init__.py is "het script dat als eerste uitgevoerd wordt wanneer het pakket wordt geïmporteerd".

Door deze eigenschap te benutten, kun je rommelige code opruimen, de API van het pakket duidelijk ontwerpen en initialisatielogica beheren, wat leidt tot krachtige mogelijkheden.


1. Maak rommelige importpaden netjes (API vereenvoudigen)

Dit is de meest nuttige en gebruikelijke manier om __init__.py te gebruiken. Het 'omhoog tillen' van klassen of functies uit submodules naar het pakketsniveau, zodat gebruikers gemakkelijker kunnen importeren.

Probleem:

Stel je voor dat views.py van Django meer dan 1.000 regels heeft en dat de functionaliteit in verschillende bestanden is gescheiden.

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

Het is erg omslachtig om deze weergaven te gebruiken in urls.py.

# payments/urls.py (slechte voorbeeld)
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 moet de interne bestandsstructuur van de views directory volledig begrijpen.


Oplossing (gebruik van init.py):

We importeren de benodigde weergaven in het init.py bestand van views.

# payments/views/__init__.py

# Til de benodigde klassen/functies naar het pakketsniveau.
from .user_views import UserProfileView
from .payment_views import PaymentView
from .webhooks import PaypalWebhookView

Nu lijkt urls.py wanneer je het views pakket gebruikt, alsof het UserProfileView direct bezit.

# payments/urls.py (verbeterd voorbeeld)

# 'views' is nu een pakket (directory) in plaats van een bestand.
from . import views

urlpatterns = [
    # Dank zij __init__.py hoef je de interne structuur niet te kennen.
    path('profile/', views.UserProfileView.as_view()),
    path('toss/', views.PaymentView.as_view()),
    path('webhook/', views.PaypalWebhookView.as_view()),
]

Of views.py nu een bestand is of een directory met meerdere bestanden, de code in urls.py blijft hetzelfde. Dit is goede abstractie.


2. Duidelijke 'officiële API' van het pakket (__all__)



from my_package import * (wildcard import) is handig, maar kan onbedoeld variabelen of modules (bijv. import os) in de huidige namespace brengen en de code vervuilen.

__all__ definieert een 'toegestane lijst' (whitelist) voor wat er geïmporteerd mag worden bij import *, "alleen de items in deze lijst mogen worden geïmporteerd".

# payments/views/__init__.py

# Modulen voor intern gebruik
import logging 
from .internal_utils import _helper_function

# Weergaven die openbaar zijn
from .user_views import UserProfileView
from .payment_views import PaymentView

# Definitie van __all__:
# "Als je import * gebruikt, kunnen alleen UserProfileView en PaymentView worden geïmporteerd."
__all__ = [
    'UserProfileView',
    'PaymentView'
]

Nu, zelfs als from payments.views import * wordt uitgevoerd, zullen de logging module of de interne functie _helper_function niet worden geïmporteerd, waardoor de namespace veilig blijft.


3. Pakketinitialisatie en metadata-definitie

Dit is een perfecte plek voor code die maar een enkele keer uitgevoerd moet worden wanneer het pakket wordt geladen.

  • Versie-informatie bieden:
# my_library/__init__.py
__version__ = "1.2.0"
__author__ = "whitedec"

Nu kan de gebruiker eenvoudig de versie controleren met import my_library; print(my_library.__version__).

  • Globale configuratie uitvoeren:

Een typisch voorbeeld is wanneer Django import django aanroept, wat intern django.setup() uitvoert, of wanneer de code die de Celery-app initialiseert in init.py is opgenomen.


# my_project/__init__.py (Celery voorbeeld)

# Zorg ervoor dat de Celery-app samen met Django wordt geladen
from .celery import app as celery_app

__all__ = ('celery_app',)


※ Let op: circulaire referenties tussen interne modules

Er zijn zaken waar je op moet letten wanneer je __init__.py als een 'informatiebalie' gebruikt.

Stel dat views/webhooks.py CancelView van views/payment_commons.py zou moeten gebruiken.

  • Slechte manier (risico op circulaire referentie):

Poging om from . import CancelView vanuit webhooks.py (in feite via init.py) te importeren.

  • Goede manier (direct importeren):

Direct importeren wat je nodig hebt, zoals from .payment_commons import CancelView in webhooks.py.

Regel:

  • Buitenlandse gebruikers (bijv. urls.py): gebruiken de API via de 'informatiebalie' (__init__.py).
  • Interne modules (bijv. webhooks.py): halen functies rechtstreeks uit de collegamodules (payment_commons.py) zonder langs de 'informatiebalie' te gaan.

Conclusie

__init__.py is geen simpel leeg bestand, maar een krachtig hulpmiddel dat kan functioneren als de 'informatiebalie', 'officiële API-documentatie', en 'initialisatie-script' voor een pakket. Als je dit bestand goed gebruikt, kan het de structuur van je project veel netter en onderhoudsvriendelijker maken.