आवलंबन एंजेक्शन: एक पायथन तरीका

पायथन में साफ और परीक्षण योग्य कोड के लिए DI पैटर्न

Page content

आश्ररण प्रवर्तन (DI) पायथन एप्लिकेशन में स्वच्छ, परीक्षणीय और बनाए रखने योग्य कोड को बढ़ावा देने वाला एक मूल डिज़ाइन पैटर्न है।

क्या आप रेस्ट एपीआईज़ बनाने के लिए फ़ास्टएपीआई बना रहे हैं, यूनिट टेस्टिंग के लिए अमल कर रहे हैं, या एवीएस लैंब्डा फ़ंक्शन के साथ काम कर रहे हैं, आश्ररण प्रवर्तन के बारे में समझ आपके कोड की गुणवत्ता को बेहतर बनाएगी।

पायथन पैकेज

आश्ररण प्रवर्तन क्या है?

आश्ररण प्रवर्तन एक डिज़ाइन पैटर्न है जिसमें घटक अपने आश्ररणों को आंतरिक रूप से नहीं बनाते बल्कि बाहरी स्रोतों से प्राप्त करते हैं। यह दृष्टिकोण घटकों को अलग करता है, जिससे आपका कोड अधिक मॉड्यूलर, परीक्षणीय और बनाए रखने योग्य होता है।

पायथन में, आश्ररण प्रवर्तन विशेष रूप से शक्तिशाली है क्योंकि भाषा की गतिशील प्रकृति और प्रोटोकॉल, अमूर्त बेस क्लासेज़, और बत्तख टाइपिंग के समर्थन के कारण। पायथन की लचीलापन के कारण आप डीआई पैटर्न को बिना भारी फ्रेमवर्क के लागू कर सकते हैं, हालांकि जब आवश्यकता हो तो फ्रेमवर्क उपलब्ध हैं।

पायथन में आश्ररण प्रवर्तन क्यों उपयोग करें?

सुधारित परीक्षणीयता: आश्ररण प्रवर्तन द्वारा, आप वास्तविक कार्यान्वयनों को मॉक या परीक्षण डबल्स के साथ आसानी से बदल सकते हैं। यह आपको ऐसे यूनिट टेस्ट लिखने की अनुमति देता है जो तेज़, अलग और डेटाबेस या एपीआई जैसी बाहरी सेवाओं की आवश्यकता नहीं होती। जब व्यापक यूनिट टेस्टिंग लिखते हैं, तो आश्ररण प्रवर्तन वास्तविक आश्ररणों को परीक्षण डबल्स के साथ बदलना बेहद आसान बनाता है।

बेहतर बनाए रखने योग्यता: आश्ररण आपके कोड में स्पष्ट होते हैं। जब आप एक कंस्ट्रक्टर को देखते हैं, तो आप तुरंत देख सकते हैं कि एक घटक क्या आवश्यकता है। यह कोडबेस को समझने और संशोधित करने में आसान बनाता है।

कम जुड़ाव: घटक अमूर्तताओं (प्रोटोकॉल या एबीसी) के बजाय वास्तविक कार्यान्वयनों पर निर्भर करते हैं। यह अर्थ है कि आप कार्यान्वयनों को बदल सकते हैं बिना निर्भर कोड को प्रभावित किए।

लचीलापन: आप विभिन्न वातावरणों (विकास, परीक्षण, उत्पादन) के लिए विभिन्न कार्यान्वयनों को विनिर्माण कर सकते हैं बिना अपने व्यवसायिक तर्क को बदले। यह विशेष रूप से उपयोगी है जब पायथन एप्लिकेशन को विभिन्न प्लेटफॉर्म्स पर तैनात करते हैं, चाहे वे एवीएस लैंब्डा हों या पारंपरिक सर्वर।

कंस्ट्रक्टर इंजेक्शन: पायथन के तरीका

पायथन में आश्ररण प्रवर्तन को लागू करने के लिए सबसे आम और आधुनिक तरीका कंस्ट्रक्टर इंजेक्शन है—__init__ विधि में आश्ररणों को पैरामीटर के रूप में स्वीकार करना।

मूल उदाहरण

यहां एक सरल उदाहरण दिया गया है जो कंस्ट्रक्टर इंजेक्शन दिखाता है:

from typing import Protocol
from abc import ABC, abstractmethod

# रिपॉज़िटरी के लिए प्रोटोकॉल की परिभाषा
class UserRepository(Protocol):
    def find_by_id(self, user_id: int) -> 'User | None':
        ...
    
    def save(self, user: 'User') -> 'User':
        ...

# सेवा रिपॉज़िटरी प्रोटोकॉल पर निर्भर करती है
class UserService:
    def __init__(self, repo: UserRepository):
        self.repo = repo
    
    def get_user(self, user_id: int) -> 'User | None':
        return self.repo.find_by_id(user_id)

यह पैटर्न स्पष्ट रूप से बताता है कि UserService को UserRepository की आवश्यकता है। आप UserService बिना एक रिपॉज़िटरी प्रदान करे बिना बना नहीं सकते, जो लापता आश्ररणों से रनटाइम त्रुटियों को रोकता है।

कई आश्ररण

जब एक घटक के पास कई आश्ररण होते हैं, तो उन्हें कंस्ट्रक्टर पैरामीटर के रूप में बस जोड़ दें:

class EmailService(Protocol):
    def send(self, to: str, subject: str, body: str) -> None:
        ...

class Logger(Protocol):
    def info(self, msg: str) -> None:
        ...
    
    def error(self, msg: str, err: Exception) -> None:
        ...

class OrderService:
    def __init__(
        self,
        repo: OrderRepository,
        email_svc: EmailService,
        logger: Logger,
        payment_svc: PaymentService,
    ):
        self.repo = repo
        self.email_svc = email_svc
        self.logger = logger
        self.payment_svc = payment_svc

प्रोटोकॉल और अमूर्त बेस क्लासेज़ का उपयोग

आश्ररण प्रवर्तन को लागू करते समय एक मुख्य सिद्धांत आश्ररण उलटा सिद्धांत (DIP) है: ऊंचे स्तर के मॉड्यूल निम्न स्तर के मॉड्यूल पर निर्भर नहीं कर सकते; दोनों अमूर्तताओं पर निर्भर कर सकते हैं।

पायथन में, आप प्रोटोकॉल (संरचनात्मक प्रकार) या अमूर्त बेस क्लासेज़ (ABCs) (नामात्मक प्रकार) के उपयोग से अमूर्तताओं को परिभाषित कर सकते हैं।

प्रोटोकॉल (पायथन 3.8+)

प्रोटोकॉल संरचनात्मक प्रकार का उपयोग करते हैं—यदि एक वस्तु आवश्यक विधियों के साथ है, तो यह प्रोटोकॉल को संतोष देता है:

from typing import Protocol

class PaymentProcessor(Protocol):
    def process_payment(self, amount: float) -> bool:
        ...

# कोई भी कक्ष जो process_payment विधि के साथ है, यह प्रोटोकॉल को संतोष देता है
class CreditCardProcessor:
    def process_payment(self, amount: float) -> bool:
        # क्रेडिट कार्ड लॉजिक
        return True

class PayPalProcessor:
    def process_payment(self, amount: float) -> bool:
        # पेपॉल लॉजिक
        return True

# सेवा कोई भी PaymentProcessor स्वीकार करती है
class OrderService:
    def __init__(self, payment_processor: PaymentProcessor):
        self.payment_processor = payment_processor

अमूर्त बेस क्लासेज़

ABCs नामात्मक प्रकार का उपयोग करते हैं—कक्षों को एबीसी से विशेष रूप से विरासत लेना आवश्यक है:

from abc import ABC, abstractmethod

class PaymentProcessor(ABC):
    @abstractmethod
    def process_payment(self, amount: float) -> bool:
        pass

class CreditCardProcessor(PaymentProcessor):
    def process_payment(self, amount: float) -> bool:
        return True

प्रोटोकॉल कब और ABCs कब उपयोग करें: प्रोटोकॉल का उपयोग करें जब आप संरचनात्मक प्रकार और लचीलापन चाहते हैं। ABCs का उपयोग करें जब आप विरासत वर्गों को अनिवार्य रूप से अनुमान लगाना या पूर्व अमल उपलब्ध कराना चाहते हैं।

वास्तविक उदाहरण: डेटाबेस अमूर्तता

पायथन एप्लिकेशन में डेटाबेस के साथ काम करते समय, आपको अक्सर डेटाबेस संचालनों को अमूर्त करना पड़ता है। यहां आश्ररण प्रवर्तन कैसे मदद करता है:

from typing import Protocol, Optional
from contextlib import contextmanager

class Database(Protocol):
    @contextmanager
    def transaction(self):
        ...
    
    def execute(self, query: str, params: dict) -> None:
        ...
    
    def fetch_one(self, query: str, params: dict) -> Optional[dict]:
        ...

# रिपॉज़िटरी अमूर्तता पर निर्भर करता है
class UserRepository:
    def __init__(self, db: Database):
        self.db = db
    
    def find_by_id(self, user_id: int) -> Optional['User']:
        result = self.db.fetch_one(
            "SELECT * FROM users WHERE id = :id",
            {"id": user_id}
        )
        if result:
            return User(**result)
        return None

यह पैटर्न आपको डेटाबेस कार्यान्वयनों (पोस्टग्रेस, एसक्यूएलाइट, मंगोडीबी) को बदलने की अनुमति देता है बिना अपने रिपॉज़िटरी कोड को बदले।

संरचना मूल पैटर्न

संरचना मूल एप्लिकेशन के प्रवेश बिंदु (आमतौर पर main.py या आपके एप्लिकेशन फैक्टरी) पर सभी आश्ररणों को संयोजित करने के स्थान है। यह आश्ररण चित्र को केंद्रित करता है और आश्ररण चित्र को स्पष्ट बनाता है।

def create_app() -> FastAPI:
    app = FastAPI()
    
    # बुनियादी आश्ररणों को आरंभ करें
    db = init_database()
    logger = init_logger()
    
    # रिपॉज़िटरी आरंभ करें
    user_repo = UserRepository(db)
    order_repo = OrderRepository(db)
    
    # आश्ररणों के साथ सेवाएं आरंभ करें
    email_svc = EmailService(logger)
    payment_svc = PaymentService(logger)
    user_svc = UserService(user_repo, logger)
    order_svc = OrderService(order_repo, email_svc, logger, payment_svc)
    
    # HTTP हैंडलर्स आरंभ करें
    user_handler = UserHandler(user_svc)
    order_handler = OrderHandler(order_svc)
    
    # मार्ग जोड़ें
    app.include_router(user_handler.router)
    app.include_router(order_handler.router)
    
    return app

यह दृष्टिकोण आपके एप्लिकेशन के ढांचे को कैसे बनाया गया है और आश्ररण कहां से आए हैं, इसके बारे में स्पष्ट रूप से बताता है। यह विशेष रूप से उपयोगी है जब आप साफ आर्किटेक्चर के सिद्धांतों के अनुसार एप्लिकेशन बना रहे हैं, जहां आपको आश्ररणों के अनेक स्तरों के बीच एकीकरण की आवश्यकता होती है।

आश्ररण प्रवर्तन फ्रेमवर्क

बड़े एप्लिकेशनों में जटिल आश्ररण चित्र के साथ काम करते समय, आश्ररणों के मैनुअल प्रबंधन बर्बर हो सकता है। पायथन में कई डीआई फ्रेमवर्क हैं जो मदद कर सकते हैं:

आश्ररण इंजेक्टर

आश्ररण इंजेक्टर एक लोकप्रिय फ्रेमवर्क है जो आश्ररण प्रवर्तन के लिए कंटेनर आधारित दृष्टिकोण प्रदान करता है।

इंस्टॉलेशन:

pip install dependency-injector

उदाहरण:

from dependency_injector import containers, providers
from dependency_injector.wiring import inject, Provide

class Container(containers.DeclarativeContainer):
    # विनिर्माण
    config = providers.Configuration()
    
    # डेटाबेस
    db = providers.Singleton(
        Database,
        connection_string=config.database.url
    )
    
    # रिपॉज़िटरी
    user_repository = providers.Factory(
        UserRepository,
        db=db
    )
    
    # सेवाएं
    user_service = providers.Factory(
        UserService,
        repo=user_repository
    )

# उपयोग
container = Container()
container.config.database.url.from_env("DATABASE_URL")
user_service = container.user_service()

इंजेक्टर

इंजेक्टर गूगल के ग्यूइस के अनुप्रेरित एक हल्का लाइब्रेरी है, जो सरलता पर ध्यान केंद्रित करता है।

इंस्टॉलेशन:

pip install injector

उदाहरण:

from injector import Injector, inject, Module, provider

class DatabaseModule(Module):
    @provider
    def provide_db(self) -> Database:
        return Database(connection_string="...")

class UserModule(Module):
    @inject
    def __init__(self, repo: UserRepository):
        self.repo = repo

injector = Injector([DatabaseModule()])
user_service = injector.get(UserService)

फ्रेमवर्क कब उपयोग करें

फ्रेमवर्क उपयोग करें जब:

  • आपका आश्ररण चित्र जटिल है और बहुत सारे आश्ररणों वाले घटक हैं
  • आपके पास एक इंटरफ़ेस के विभिन्न कार्यान्वयन हैं जिनका चयन कॉन्फ़िगरेशन के आधार पर करना है
  • आपको स्वचालित आश्ररण रिज़ॉल्यूशन चाहिए
  • आप एक बड़े एप्लिकेशन बना रहे हैं जहां मैनुअल वायरिंग त्रुटिपूर्ण हो जाता है

मैनुअल डीआई के साथ रहें जब:

  • आपका एप्लिकेशन छोटा से माध्यमिक आकार का है
  • आश्ररण चित्र सरल है और आसानी से अनुसरण करने योग्य है
  • आप आश्ररणों को न्यूनतम और स्पष्ट रखना चाहते हैं
  • आप फ्रेमवर्क के जादू के बजाय अपने कोड को स्पष्ट रखना चाहते हैं

परीक्षण आश्ररण प्रवर्तन के साथ

आश्ररण प्रवर्तन का एक प्रमुख लाभ सुधारित परीक्षणीयता है। यहां डीआई कैसे परीक्षण करना आसान बनाता है:

यूनिट परीक्षण उदाहरण

from unittest.mock import Mock
import pytest

# परीक्षण के लिए मॉक कार्यान्वयन
class MockUserRepository:
    def __init__(self):
        self.users = {}
        self.error = None
    
    def find_by_id(self, user_id: int) -> Optional['User']:
        if self.error:
            raise self.error
        return self.users.get(user_id)
    
    def save(self, user: 'User') -> 'User':
        if self.error:
            raise self.error
        self.users[user.id] = user
        return user

# परीक्षण करते समय मॉक का उपयोग
def test_user_service_get_user():
    mock_repo = MockUserRepository()
    mock_repo.users[1] = User(id=1, name="जॉन", email="जॉन@example.com")
    
    service = UserService(mock_repo)
    
    user = service.get_user(1)
    assert user is not None
    assert user.name == "जॉन"

यह परीक्षण तेज़ी से चलता है, डेटाबेस की आवश्यकता नहीं होती है, और आपके व्यवसायिक तर्क को अलग रखता है। जब पायथन में यूनिट परीक्षणिंग के साथ काम करते हैं, तो आश्ररण प्रवर्तन परीक्षण डबल्स बनाने और इंटरैक्शन की जांच करने में आसान बनाता है।

पायस्ट फिक्सचर्स का उपयोग

पायस्ट फिक्सचर्स आश्ररण प्रवर्तन के साथ बेहतर तरीके से काम करते हैं:

@pytest.fixture
def mock_user_repository():
    return MockUserRepository()

@pytest.fixture
def user_service(mock_user_repository):
    return UserService(mock_user_repository)

def test_user_service_get_user(user_service, mock_user_repository):
    user = User(id=1, name="जॉन", email="जॉन@example.com")
    mock_user_repository.users[1] = user
    
    result = user_service.get_user(1)
    assert result.name == "जॉन"

सामान्य पैटर्न और श्रेष्ठ अभ्यास

1. इंटरफ़ेस विभाजन का उपयोग करें

प्रोटोकॉल और इंटरफ़ेस को छोटे और एक वास्तविक आवश्यकता पर फोकस करें:

# अच्छा: ग्राहक केवल उपयोगकर्ता पढ़ने के लिए आवश्यकता है
class UserReader(Protocol):
    def find_by_id(self, user_id: int) -> Optional['User']:
        ...
    
    def find_by_email(self, email: str) -> Optional['User']:
        ...

# लिखने के लिए अलग इंटरफ़ेस
class UserWriter(Protocol):
    def save(self, user: 'User') -> 'User':
        ...
    
    def delete(self, user_id: int) -> bool:
        ...

2. कंस्ट्रक्टर में आश्ररणों की जांच करें

कंस्ट्रक्टर आश्ररणों की जांच करें और यदि प्रारंभीकरण विफल हो जाता है तो स्पष्ट त्रुटियां उठाएं:

class UserService:
    def __init__(self, repo: UserRepository):
        if repo is None:
            raise ValueError("उपयोगकर्ता रिपॉज़िटरी शून्य नहीं हो सकता")
        self.repo = repo

3. प्रकार के संकेतों का उपयोग करें

प्रकार के संकेत आश्ररणों को स्पष्ट बनाते हैं और आईडीई समर्थन और स्थिर प्रकार की जांच के लिए मदद करते हैं:

from typing import Protocol, Optional

class UserService:
    def __init__(
        self,
        repo: UserRepository,
        logger: Logger,
        email_service: EmailService,
    ) -> None:
        self.repo = repo
        self.logger = logger
        self.email_service = email_service

4. अतिरिक्त इंजेक्शन से बचें

अंतर्निहित आश्ररणों के वास्तविक अंतर्निहित विवरणों को इंजेक्ट न करें। यदि एक घटक अपने सहायक वस्तुओं को बनाता और प्रबंधित करता है, तो वह ठीक है:

# अच्छा: आंतरिक सहायक को इंजेक्शन की आवश्यकता नहीं है
class UserService:
    def __init__(self, repo: UserRepository):
        self.repo = repo
        # आंतरिक कैश - इंजेक्शन की आवश्यकता नहीं है
        self._cache: dict[int, User] = {}

5. आश्ररणों के बारे में दस्तावेज़ीकरण करें

दस्तावेज़ीकरण द्वारा आश्ररणों की आवश्यकता के बारे में और कोई भी अभिसरण बताएं:

class UserService:
    """UserService उपयोगकर्ता संबंधी व्यवसाय तर्क का प्रबंधन करता है।
    
    तर्क:
        repo: UserRepository डेटा एक्सेस के लिए। यदि समानांतर संदर्भ में उपयोग किया जाता है, तो यह धागा-सुरक्षित होना चाहिए
        logger: त्रुटि प्रायोजन और डीबगिंग के लिए लॉगर।
    """
    def __init__(self, repo: UserRepository, logger: Logger):
        self.repo = repo
        self.logger = logger

फ़ास्टएपीआई के साथ आश्ररण प्रवर्तन

फ़ास्टएपीआई में डिपेंडेंसी इंजेक्शन के लिए डिपेंड्स मैकेनिज़म के माध्यम से बुनियादी समर्थन है:

from fastapi import FastAPI, Depends
from typing import Annotated

app = FastAPI()

def get_user_repository() -> UserRepository:
    db = get_database()
    return UserRepository(db)

def get_user_service(
    repo: Annotated[UserRepository, Depends(get_user_repository)]
) -> UserService:
    return UserService(repo)

@app.get("/users/{user_id}")
def get_user(
    user_id: int,
    service: Annotated[UserService, Depends(get_user_service)]
):
    user = service.get_user(user_id)
    if not user:
        raise HTTPException(status_code=404)
    return user

फ़ास्टएपीआई के डिपेंडेंसी इंजेक्शन प्रणाली आश्ररण चित्र को स्वचालित रूप से प्रबंधित करता है, जिससे आपको साफ, बनाए रखने योग्य एपीआईज़ बनाना आसान बन जाता है।

आश्ररण प्रवर्तन कब नहीं उपयोग करें

आश्ररण प्रवर्तन एक शक्तिशाली उपकरण है, लेकिन यह हमेशा आवश्यक नहीं है:

डीआई के बिना बचें:

  • सरल मूल्य वस्तुएं या डेटा क्लासेज़
  • आंतरिक सहायक फ़ंक्शन या उपकरण
  • एक बार के स्क्रिप्ट या छोटे उपकरण
  • जब सीधा निर्माण स्पष्ट और सरल है

डीआई न करें जब के उदाहरण:

# सरल डेटा क्लास - डीआई की आवश्यकता नहीं
from dataclasses import dataclass

@dataclass
class Point:
    x: float
    y: float

# सरल उपकरण - डीआई की आवश्यकता नहीं
def format_currency(amount: float) -> str:
    return f"${amount:.2f}"

पायथन परिसर के साथ एकीकरण

आश्ररण प्रवर्तन अन्य पायथन पैटर्न और उपकरणों के साथ बेहतर तरीके से काम करता है। जब आप पायथन पैकेज या यूनिट परीक्षण फ़्रेमवर्क के साथ एप्लिकेशन बना रहे हैं, तो आप अपने व्यवसायिक तर्क में इन सेवाओं को इंजेक्ट कर सकते हैं:

class ReportService:
    def __init__(
        self,
        pdf_generator: PDFGenerator,
        repo: ReportRepository,
        logger: Logger,
    ):
        self.pdf_generator = pdf_generator
        self.repo = repo
        self.logger = logger
    
    def generate_report(self, report_id: int) -> bytes:
        report_data = self.repo.get_by_id(report_id)
        pdf = self.pdf_generator.generate(report_data)
        self.logger.info(f"जनरेट किया गया रिपोर्ट {report_id}")
        return pdf

यह आपको परीक्षण के दौरान उपयोग करने योग्य कार्यान्वयनों को बदलने या मॉक करने की अनुमति देता है।

एसिंक्रोनस आश्ररण प्रवर्तन

पायथन के एसिंक्रोनस/एवेट सिंटैक्स आश्ररण प्रवर्तन के साथ अच्छा काम करता है:

from typing import Protocol
import asyncio

class AsyncUserRepository(Protocol):
    async def find_by_id(self, user_id: int) -> Optional['User']:
        ...
    
    async def save(self, user: 'User') -> 'User':
        ...

class AsyncUserService:
    def __init__(self, repo: AsyncUserRepository):
        self.repo = repo
    
    async def get_user(self, user_id: int) -> Optional['User']:
        return await self.repo.find_by_id(user_id)
    
    async def get_users_batch(self, user_ids: list[int]) -> list['User']:
        tasks = [self.repo.find_by_id(uid) for uid in user_ids]
        results = await asyncio.gather(*tasks)
        return [u for u in results if u is not None]

निष्कर्ष

आश्ररण प्रवर्तन लिखे गए बनाए रखने योग्य, परीक्षणीय पायथन कोड के लिए एक मूल ढांचा है। इस लेख में बताए गए पैटर्नों के साथ—कंस्ट्रक्टर इंजेक्शन, प्रोटोकॉल-आधारित डिज़ाइन और संरचना मूल पैटर्न के उपयोग से—आप आसानी से समझे, परीक्षण करे और संशोधित करे वाले एप्लिकेशन बनाएंगे।

छोटे से माध्यमिक एप्लिकेशन के लिए मैनुअल कंस्ट्रक्टर इंजेक्शन से शुरू करें और जब आश्ररण चित्र बढ़ता है तो डिपेंडेंसी-इंजेक्टर या इंजेक्टर जैसे फ्रेमवर्क के बारे में सोचें। याद रखें कि लक्ष्य स्पष्टता और परीक्षणीयता है, न कि जटिलता के लिए।

अधिक पायथन विकास संसाधनों के लिए, हमारे पायथन चेटशीट को देखें, जो पायथन सिंटैक्स और सामान्य पैटर्न पर त्वरित संदर्भ के लिए है।

उपयोगी लिंक

बाहरी संसाधन