डिपेंडेंसी इंजेक्शन के लिए पायथोनिक तरीका क्या है?


84

परिचय

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

अब पाइथन के लिए, आप वैसे ही कर सकते हैं, लेकिन मुझे लगता है कि पाइथन के मामले में यह तरीका बहुत ज्यादा सही था। तो फिर आप इसे पायथोनिक तरीके से कैसे लागू करेंगे?

उदाहरण

कहो कि यह फ्रेमवर्क कोड है:

class FrameworkClass():
    def __init__(self, ...):
        ...

    def do_the_job(self, ...):
        # some stuff
        # depending on some external function

मूल दृष्टिकोण

सबसे भोली (और शायद सबसे अच्छा?) तरीका यह है कि बाहरी फ़ंक्शन को FrameworkClassकंस्ट्रक्टर में आपूर्ति करने की आवश्यकता होती है , और फिर do_the_jobविधि से लागू किया जाता है ।

फ्रेमवर्क कोड:

class FrameworkClass():
    def __init__(self, func):
        self.func = func

    def do_the_job(self, ...):
        # some stuff
        self.func(...)

क्लाइंट कोड:

def my_func():
    # my implementation

framework_instance = FrameworkClass(my_func)
framework_instance.do_the_job(...)

सवाल

सवाल छोटा है। क्या ऐसा करने के लिए कोई बेहतर आमतौर पर इस्तेमाल किया जाने वाला पाइथोनिक तरीका है? या हो सकता है कि इस तरह की कार्यक्षमता का समर्थन करने वाले कोई पुस्तकालय?

अद्यतन: ठोस स्थिति

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

जाहिर है, फ्रेमवर्क उपयोगकर्ताओं या किसी अन्य एप्लिकेशन विशिष्ट तर्क के बारे में कुछ भी नहीं जानता है, इसलिए क्लाइंट कोड को प्रमाणीकरण कार्य करने के लिए उपयोगकर्ता गेट्टर कार्यक्षमता को फ्रेमवर्क में इंजेक्ट करना होगा।


2
आप "कार्यान्वित होने के लिए एक इंटरफ़ेस क्यों नहीं प्रदान करते हैं और आपके फ्रेमवर्क कोड में एक वर्ग की आवृत्ति को स्वीकार करते हैं जो परिभाषित इंटरफ़ेस को लागू करता है" ? पायथन में आप इसे ईएएफपी शैली में करेंगे (यानी मान लें कि यह उस इंटरफ़ेस से मिलता है और AttributeErrorया TypeErrorअन्यथा उठाया जाता है), लेकिन अन्यथा यह समान है।
जोंशरशेप

इसे का उपयोग करने के लिए आसान है absके ABCMetaसाथ metaclass @abstractmethodडेकोरेटर, और कोई मैनुअल मान्यता। बस विकल्प और सुझाव के कुछ प्राप्त करना चाहते हैं। आपके द्वारा उद्धृत एक सबसे साफ है, लेकिन मुझे लगता है कि अधिक ओवरहेड के साथ।
बैग्रेट

तब मुझे नहीं पता कि आप क्या सवाल पूछना चाहते हैं।
जोंशरशेप

ठीक है, मैं दूसरे शब्दों में कोशिश करूंगा। समस्या स्पष्ट है। सवाल यह है कि पाइथोनिक तरीके से ऐसा कैसे किया जाए। विकल्प 1 : आपके द्वारा उद्धृत किया गया तरीका, विकल्प 2 : प्रश्न में वर्णित मूल दृष्टिकोण । तो सवाल यह है कि ऐसा करने के लिए किसी भी अन्य पायथोनिक तरीके?
बैग्रेट

जवाबों:


66

रेमंड हेटिंगर को देखें - सुपर को सुपर माना जाता है! - पाइकॉन 2015 डीआई के बजाय सुपर और मल्टीपल इनहेरिटेंस का उपयोग करने के एक तर्क के लिए। यदि आपके पास पूरा वीडियो देखने का समय नहीं है, तो मिनट 15 पर जाएं (लेकिन मैं यह सब देखने की सलाह दूंगा)।

यहाँ एक उदाहरण दिया गया है कि इस वीडियो में आपके उदाहरण के अनुसार क्या लागू किया जाए:

फ्रेमवर्क कोड:

class TokenInterface():
    def getUserFromToken(self, token):
        raise NotImplementedError

class FrameworkClass(TokenInterface):
    def do_the_job(self, ...):
        # some stuff
        self.user = super().getUserFromToken(...)

क्लाइंट कोड:

class SQLUserFromToken(TokenInterface):
    def getUserFromToken(self, token):      
        # load the user from the database
        return user

class ClientFrameworkClass(FrameworkClass, SQLUserFromToken):
    pass

framework_instance = ClientFrameworkClass()
framework_instance.do_the_job(...)

यह काम करेगा क्योंकि पायथन एमआरओ गारंटी देगा कि getUserFromToken क्लाइंट विधि कहा जाता है (यदि सुपर () का उपयोग किया जाता है)। यदि आप अजगर 2.x पर हैं तो कोड बदलना होगा।

यहां एक अतिरिक्त लाभ यह है कि यदि ग्राहक कोई कार्यान्वयन प्रदान नहीं करता है तो यह एक अपवाद को बढ़ाएगा।

बेशक, यह वास्तव में निर्भरता इंजेक्शन नहीं है, यह कई विरासत और मिश्रण है, लेकिन यह आपकी समस्या को हल करने का एक पायथनिक तरीका है।


10
यह उत्तर माना जाता है super():)
bagrat

2
रेमंड ने इसे सीआई कहा, जबकि मैंने सोचा, कि यह एक शुद्ध मिश्रण है। लेकिन क्या ऐसा हो सकता है कि पायथन में मिक्सिन और सीआई लगभग एक जैसे हों? अंतर केवल इन्द्रिय के स्तर का है। मिक्सिन निर्भरता को एक वर्ग स्तर पर इंजेक्ट करता है जबकि CI एक उदाहरण में निर्भरता को इंजेक्ट करता है।
nad2000

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

6
जब मुझे यह बहुत ही सुंदर लगता है, तो मुझे इस दृष्टिकोण से दो समस्याएँ हैं: 1. क्या होता है जब आपको अपनी कक्षा में इंजेक्ट की जाने वाली सर्वर संबंधी वस्तुओं की आवश्यकता होती है? 2. इनहेरिटेंस का प्रयोग अक्सर "a" / विशेषज्ञता-बोध में किया जाता है। DI के लिए इसका उपयोग उस विचार को धता बताता है (उदाहरण के लिए यदि मैं किसी सेवा को प्रस्तुतकर्ता में इंजेक्ट करना चाहता हूं)।
AljoSt

18

जिस तरह से हम अपनी परियोजना में निर्भरता इंजेक्शन करते हैं वह इंजेक्शन लीब का उपयोग करके है । की जाँच करें प्रलेखन । मैं अत्यधिक DI के लिए इसका उपयोग करने की सलाह देता हूं। यह थोड़े से केवल एक फ़ंक्शन के साथ कोई मतलब नहीं रखता है, लेकिन जब आप कई डेटा स्रोतों आदि का प्रबंधन करना चाहते हैं, तो बहुत सारी समझ बनाने लगते हैं, आदि।

अपने उदाहरण के बाद यह कुछ इस तरह हो सकता है:

# framework.py
class FrameworkClass():
    def __init__(self, func):
        self.func = func

    def do_the_job(self):
        # some stuff
        self.func()

आपका कस्टम फ़ंक्शन:

# my_stuff.py
def my_func():
    print('aww yiss')

आवेदन में कहीं आप बूटस्ट्रैप फ़ाइल बनाना चाहते हैं जो सभी परिभाषित निर्भरताओं पर नज़र रखता है:

# bootstrap.py
import inject
from .my_stuff import my_func

def configure_injection(binder):
    binder.bind(FrameworkClass, FrameworkClass(my_func))

inject.configure(configure_injection)

और फिर आप इस तरह से कोड का उपभोग कर सकते हैं:

# some_module.py (has to be loaded with bootstrap.py already loaded somewhere in your app)
import inject
from .framework import FrameworkClass

framework_instance = inject.instance(FrameworkClass)
framework_instance.do_the_job()

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

इसलिए सीधे आपके सवाल का जवाब देना बहुत कठिन होगा। मुझे लगता है कि असली सवाल यह है: क्या अजगर को डीआई के लिए कुछ मूल समर्थन है? और जवाब है, दुख की बात है: नहीं।


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

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

14

कुछ समय पहले मैंने Pythonic - Dependency Injector बनाने के लिए एक महत्वाकांक्षा के साथ निर्भरता इंजेक्शन microframework लिखा था । इसके उपयोग के मामले में आपका कोड कैसा दिख सकता है:

"""Example of dependency injection in Python."""

import logging
import sqlite3

import boto.s3.connection

import example.main
import example.services

import dependency_injector.containers as containers
import dependency_injector.providers as providers


class Platform(containers.DeclarativeContainer):
    """IoC container of platform service providers."""

    logger = providers.Singleton(logging.Logger, name='example')

    database = providers.Singleton(sqlite3.connect, ':memory:')

    s3 = providers.Singleton(boto.s3.connection.S3Connection,
                             aws_access_key_id='KEY',
                             aws_secret_access_key='SECRET')


class Services(containers.DeclarativeContainer):
    """IoC container of business service providers."""

    users = providers.Factory(example.services.UsersService,
                              logger=Platform.logger,
                              db=Platform.database)

    auth = providers.Factory(example.services.AuthService,
                             logger=Platform.logger,
                             db=Platform.database,
                             token_ttl=3600)

    photos = providers.Factory(example.services.PhotosService,
                               logger=Platform.logger,
                               db=Platform.database,
                               s3=Platform.s3)


class Application(containers.DeclarativeContainer):
    """IoC container of application component providers."""

    main = providers.Callable(example.main.main,
                              users_service=Services.users,
                              auth_service=Services.auth,
                              photos_service=Services.photos)

इस उदाहरण के और अधिक व्यापक वर्णन के लिए एक लिंक है - http://python - d dependency-injector.ets-labs.org/examples/services_miniapp.html

आशा है कि यह थोड़ा मदद कर सकता है। अधिक जानकारी के लिये कृपया यहां देखें:


धन्यवाद @Roman मोगिलतोव मैं यह जानने के लिए उत्सुक हूं कि आप इन कंटेनरों को रनटाइम में कैसे कॉन्फ़िगर / अनुकूलित करते हैं, एक कॉन्फ़िगरेशन फ़ाइल से कहते हैं। ऐसा लगता है कि ये निर्भरताएं दिए गए कंटेनर ( Platformऔर Services) में हार्डकोड की गई हैं । इंजेक्शन योग्य पुस्तकालय वर्गों के हर संयोजन के लिए एक नया कंटेनर बनाने का समाधान है?
बिल डीरोज

2
हाय @BillDeRose। जबकि मेरे जवाब को एसओ टिप्पणी के रूप में बहुत लंबा माना जाता था, मैंने एक गितुब मुद्दा बनाया है और वहां अपना उत्तर पोस्ट कर रहा हूं - github.com/ets-labs/python-d dependency - injector / issues / 197 :) उम्मीद है कि यह मदद करता है। धन्यवाद, रोमन
रोमन मोगिलतोव

3

निर्भरता इंजेक्शन एक सरल तकनीक है जो सीधे पायथन का समर्थन करती है। कोई अतिरिक्त पुस्तकालयों की आवश्यकता नहीं है। प्रकार संकेत का उपयोग स्पष्टता और पठनीयता में सुधार कर सकते हैं।

फ्रेमवर्क कोड:

class UserStore():
    """
    The base class for accessing a user's information.
    The client must extend this class and implement its methods.
    """
    def get_name(self, token):
        raise NotImplementedError

class WebFramework():
    def __init__(self, user_store: UserStore):
        self.user_store = user_store

    def greet_user(self, token):
        user_name = self.user_store.get_name(token)
        print(f'Good day to you, {user_name}!')

क्लाइंट कोड:

class AlwaysMaryUser(UserStore):
    def get_name(self, token):      
        return 'Mary'

class SQLUserStore(UserStore):
    def __init__(self, db_params):
        self.db_params = db_params

    def get_name(self, token):
        # TODO: Implement the database lookup
        raise NotImplementedError

client = WebFramework(AlwaysMaryUser())
client.greet_user('user_token')

UserStoreवर्ग और प्रकार हिंट निर्भरता इंजेक्शन लागू करने के लिए आवश्यक नहीं हैं। उनका प्राथमिक उद्देश्य क्लाइंट डेवलपर को मार्गदर्शन प्रदान करना है। यदि आप UserStoreकक्षा को हटाते हैं और इसके लिए सभी संदर्भ आते हैं, तो कोड अभी भी काम करता है।


2

मुझे लगता है कि विशिष्ट पायथन डेवलपर्स की वरीयताओं के कारण DI और संभवतः AOP को आमतौर पर Pythonic नहीं माना जाता है, बल्कि यह कि भाषा सुविधाएँ।

तथ्य की बात के रूप में आप <100 लाइनों में एक बुनियादी डीआई ढांचे को लागू कर सकते हैं , मेटाक्लासेस और क्लास डेकोरेटर्स का उपयोग कर सकते हैं।

कम इनवेसिव समाधान के लिए, इन निर्माणों का उपयोग जेनेरिक ढांचे में प्लग-इन कस्टम कार्यान्वयन के लिए किया जा सकता है।


2

Google द्वारा एक खुला स्रोत अजगर आश्रित इंजेक्टर, पिनजेक्ट भी है।

यहाँ एक उदाहरण है

>>> class OuterClass(object):
...     def __init__(self, inner_class):
...         self.inner_class = inner_class
...
>>> class InnerClass(object):
...     def __init__(self):
...         self.forty_two = 42
...
>>> obj_graph = pinject.new_object_graph()
>>> outer_class = obj_graph.provide(OuterClass)
>>> print outer_class.inner_class.forty_two
42

और यहाँ स्रोत कोड है


1

निर्भरता इंजेक्शन करने के लिए एक बहुत ही आसान और पायथोनिक तरीका आयात करना है।

आप एक छोटे उपयोगिता फ़ंक्शन को परिभाषित कर सकते हैं

def inject_method_from_module(modulename, methodname):
    """
    injects dynamically a method in a module
    """
    mod = importlib.import_module(modulename)
    return getattr(mod, methodname, None)

और फिर आप इसका उपयोग कर सकते हैं:

myfunction = inject_method_from_module("mypackage.mymodule", "myfunction")
myfunction("a")

Mypackage / mymodule.py में आप myfunction को परिभाषित करते हैं

def myfunction(s):
    print("myfunction in mypackage.mymodule called with parameter:", s)

आप निश्चित रूप से एक वर्ग MyClass iso का भी उपयोग कर सकते हैं। समारोह myfunction। यदि आप सेटिंग के फ़ाइल में मेथडनाम के मान को परिभाषित करते हैं, तो आप सेटिंग फ़ाइल के मान के आधार पर मेथडनेम के विभिन्न संस्करणों को लोड कर सकते हैं। Django अपने डेटाबेस कनेक्शन को परिभाषित करने के लिए इस तरह की योजना का उपयोग कर रहा है।


1

पायथन ओओपी कार्यान्वयन के कारण, आईओसी और निर्भरता इंजेक्शन पायथन दुनिया में मानक अभ्यास नहीं हैं। लेकिन दृष्टिकोण पायथन के लिए भी आशाजनक लगता है।

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

तो मेरा समाधान है:

# Framework internal
def MetaIoC(name, bases, namespace):
    cls = type("IoC{}".format(name), tuple(), namespace)
    return type(name, bases + (cls,), {})


# Entities level                                        
class Entity:
    def _lower_level_meth(self):
        raise NotImplementedError

    @property
    def entity_prop(self):
        return super(Entity, self)._lower_level_meth()


# Adapters level
class ImplementedEntity(Entity, metaclass=MetaIoC):          
    __private = 'private attribute value'                    

    def __init__(self, pub_attr):                            
        self.pub_attr = pub_attr                             

    def _lower_level_meth(self):                             
        print('{}\n{}'.format(self.pub_attr, self.__private))


# Infrastructure level                                       
if __name__ == '__main__':                                   
    ENTITY = ImplementedEntity('public attribute value')     
    ENTITY.entity_prop         

संपादित करें:

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


बेशक IOC और DI का आमतौर पर उपयोग किया जाता है, जो आमतौर पर उपयोग नहीं किया जाता है वह डीआई फ्रेमवर्क हैं , बेहतर या बदतर के लिए।
juanpa.arrivillaga

1

अजगर में डीआई फ्रेमवर्क में से कुछ के साथ खेलने के बाद, मैंने पाया है कि उन्होंने .NET कोर जैसे अन्य स्थानों में कितना सरल है, इसकी तुलना करते हुए उपयोग करने के लिए थोड़ा भद्दा महसूस किया है। यह ज्यादातर डेकोरेटर जैसी चीजों के साथ जुड़ने के कारण होता है जो कोड को अव्यवस्थित करते हैं और इसे केवल एक प्रोजेक्ट में जोड़ने या हटाने या चर नामों के आधार पर इसे जोड़ने के लिए कठिन बनाते हैं।

मैं हाल ही में एक निर्भरता इंजेक्शन ढांचे पर काम कर रहा हूं जो सरल-इंजेक्शन नामक इंजेक्शन को करने के लिए टाइपिंग एनोटेशन का उपयोग करता है। नीचे एक सरल उदाहरण है

from simple_injection import ServiceCollection


class Dependency:
    def hello(self):
        print("Hello from Dependency!")

class Service:
    def __init__(self, dependency: Dependency):
        self._dependency = dependency

    def hello(self):
        self._dependency.hello()

collection = ServiceCollection()
collection.add_transient(Dependency)
collection.add_transient(Service)

collection.resolve(Service).hello()
# Outputs: Hello from Dependency!

यह पुस्तकालय सेवा जीवनकाल और कार्यान्वयन के लिए बाध्यकारी सेवाओं का समर्थन करता है।

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

उम्मीद है की यह मदद करेगा। अधिक जानकारी के लिए, कृपया देखें

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.