wake-up-neo.net

Perché IoC / DI non è comune in Python?

In Java IoC / DI è una pratica molto comune ampiamente utilizzata nelle applicazioni Web , quasi tutti i framework disponibili e Java EE. D'altra parte, ci sono anche un sacco di grandi Python, ma accanto a Zope (che io ' ho sentito che dovrebbe essere davvero orribile da codificare) IoC non sembra essere molto comune nel Python. (Per favore, nomina alcuni esempi se pensi che mi sbagli)).

Esistono ovviamente diversi cloni di popolari Java framework IoC disponibili per Python, springpython per esempio. Ma nessuno di loro sembra abituarsi praticamente. Almeno, io ' non sono mai inciampato in un Django o sqlalchemy + <insert your favorite wsgi toolkit here> applicazione web basata che usa qualcosa del genere.

A mio avviso, IoC presenta vantaggi ragionevoli e semplificherebbe, ad esempio, la sostituzione del modello utente predefinito di Django, ma l'utilizzo estensivo di classi di interfaccia e IoC in Python sembra un po 'strano e non "Pythonic". Ma forse qualcuno ha una spiegazione migliore, perché l'IoC non è ampiamente usato in Python.

281
tux21b

In realtà non penso che DI/IoC siano quello non comune in Python. Ciò che è non comune, tuttavia, sono DI/IoC framework/container .

Pensaci: cosa fa un contenitore DI? Te lo permette

  1. collega componenti indipendenti in un'applicazione completa ...
  2. ... in fase di esecuzione.

Abbiamo nomi per "cablaggio insieme" e "in fase di esecuzione":

  1. scripting
  2. dinamico

Quindi, un contenitore DI non è altro che un interprete per un linguaggio di scripting dinamico. In realtà, lasciami riformulare questo: un tipico contenitore DI Java/.NET non è altro che un maledetto interprete per un linguaggio di scripting dinamico davvero pessimo con sintassi brutte, a volte basate su XML.

Quando programmi in Python, perché dovresti usare un brutto e brutto linguaggio di scripting quando hai un linguaggio di scripting bello e brillante a tua disposizione? In realtà, questa è una domanda più generale: quando programmi in quasi tutte le lingue, perché dovresti usare un brutto e brutto linguaggio di scripting quando hai Jython e IronPython a tua disposizione?

Quindi, per ricapitolare: la pratica di DI/IoC è altrettanto importante in Python come in Java, per gli stessi identici motivi. L'implementazione di DI/IoC, tuttavia, è incorporata nel linguaggio e spesso così leggera da svanire completamente.

(Ecco un breve accenno per un'analogia: in Assembly, una chiamata di subroutine è un affare piuttosto importante: devi salvare le variabili locali e i registri in memoria, salvare l'indirizzo di ritorno da qualche parte, cambiare il puntatore delle istruzioni alla subroutine che stai chiamando, fai in modo che salti in qualche modo nella tua subroutine quando è finita, metti gli argomenti da qualche parte dove la chiamante può trovarli, e così via. IOW: in Assembly, "subroutine call" è un Design Pattern, e prima c'erano linguaggi come Fortran che aveva chiamate subroutine incorporate, le persone stavano costruendo i propri "framework subroutine". Diresti che le chiamate subroutine sono "non comuni" in Python, solo perché non usi i framework subroutine?)

A proposito: per un esempio di come sembra portare DI alla sua logica conclusione, dai un'occhiata a Gilad Bracha 's Newspeak Programming Language e ai suoi scritti sull'argomento:

187
Jörg W Mittag

Parte di esso è il modo in cui il sistema di moduli funziona in Python. Puoi ottenere una sorta di "singleton" gratuitamente, semplicemente importandolo da un modulo. Definire un'istanza effettiva di un oggetto in un modulo, quindi qualsiasi codice client può importarlo e ottenere effettivamente un oggetto funzionante, completamente costruito/popolato.

Ciò è in contrasto con Java, dove non si importano istanze effettive di oggetti. Ciò significa che devi sempre istanziarli tu stesso (o utilizzare una sorta di approccio in stile IoC/DI). Puoi mitigare il fastidio di dover istanziare tutto da solo disponendo di metodi factory statici (o classi factory effettive), ma poi incorri ancora nel sovraccarico di risorse di crearne di nuovi ogni volta.

46
TM.

Django fa grande uso dell'inversione del controllo. Ad esempio, il server di database è selezionato dal file di configurazione, quindi il framework fornisce le istanze appropriate del wrapper di database ai client del database.

La differenza è che Python ha tipi di prima classe. I tipi di dati, comprese le classi, sono essi stessi oggetti. Se vuoi che qualcosa usi una determinata classe, semplicemente chiama la classe. Ad esempio:

if config_dbms_name == 'postgresql':
    import psycopg
    self.database_interface = psycopg
Elif config_dbms_name == 'mysql':
    ...

Il codice successivo può quindi creare un'interfaccia del database scrivendo:

my_db_connection = self.database_interface()
# Do stuff with database.

Invece delle funzioni factory di plateplate che Java e C++ necessitano, Python lo fa con una o due righe di codice ordinario. Questa è la forza del funzionale rispetto all'imperativo programmazione.

36
Daniel Newby

IoC e DI sono super comuni in mature Python. Non è necessario un framework per implementare DI grazie alla digitazione duck.

Il miglior esempio è come si configura un Django usando settings.py:

# settings.py
CACHES = {
    'default': {
        'BACKEND': 'Django_redis.cache.RedisCache',
        'LOCATION': REDIS_URL + '/1',
    },
    'local': {
        'BACKEND': 'Django.core.cache.backends.locmem.LocMemCache',
        'LOCATION': 'snowflake',
    }
}

Django Rest Framework utilizza DI pesantemente:

class FooView(APIView):
    # The "injected" dependencies:
    permission_classes = (IsAuthenticated, )
    throttle_classes = (ScopedRateThrottle, )
    parser_classes = (parsers.FormParser, parsers.JSONParser, parsers.MultiPartParser)
    renderer_classes = (renderers.JSONRenderer,)

    def get(self, request, *args, **kwargs):
        pass

    def post(self, request, *args, **kwargs):
        pass

Lasciami ricordare ( fonte ):

"Iniezione delle dipendenze" è un termine di 25 dollari per un concetto da 5 cent. [...] Iniezione di dipendenza significa dare a un oggetto le sue variabili di istanza. [...].

30
Max Malysh

Non ho usato Python in diversi anni, ma direi che ha più a che fare con il fatto di essere un linguaggio tipizzato dinamicamente di qualsiasi altra cosa. Per un semplice esempio, in Java, se volessi per provare che qualcosa ha scritto per standardizzare in modo appropriato potrei usare DI e passare qualsiasi PrintStream per catturare il testo che sta scrivendo e verificarlo. Quando sto lavorando in Ruby, tuttavia, posso sostituire dinamicamente il metodo 'put' su STDOUT per fai la verifica, lasciando DI completamente fuori dal quadro. Se l'unica ragione per cui sto creando un'astrazione è testare la classe che la sta usando (pensa alle operazioni del file system o all'orologio in Java), allora DI/IoC crea complessità inutili nel soluzione.

12
bcarlso

IoC/DI è un concetto di design, ma sfortunatamente è spesso preso come un concetto che si applica a determinate lingue (o sistemi di battitura). Mi piacerebbe vedere i contenitori per iniezione di dipendenza diventare molto più popolari in Python. C'è Spring, ma questo è un super-framework e sembra essere una porta diretta dei concetti Java senza molta considerazione per "The Python Way."

Date le annotazioni in Python 3, ho deciso di avere una crepa in un contenitore di iniezione di dipendenza completo, ma semplice: https://github.com/zsims/dic =. Si basa su alcuni concetti di un contenitore di iniezione di dipendenze .NET (che IMO è fantastico se stai giocando in quello spazio), ma mutato con Python.

9
zsims

Sembra che le persone non capiscano davvero cosa significhino l'iniezione di dipendenza e l'inversione del controllo.

La pratica di utilizzare l'inversione del controllo consiste nell'avere classi o funzioni che dipendono da altre classi o funzioni, ma invece di creare le istanze all'interno della classe del codice funzione è meglio riceverlo come parametro, in modo che l'accoppiamento libero possa essere archiviato. Ciò ha molti vantaggi in quanto maggiore testabilità e l'archiviazione del principio di sostituzione di Mosca.

Vedi, lavorando con interfacce e iniezioni, il tuo codice diventa più gestibile, dal momento che puoi cambiare facilmente il comportamento, perché non dovrai riscrivere una singola riga di codice (forse una o due righe sulla configurazione DI) del tuo classe per modificarne il comportamento, poiché le classi che implementano l'interfaccia che la tua classe sta aspettando possono variare indipendentemente fintanto che seguono l'interfaccia. Una delle migliori strategie per mantenere il codice disaccoppiato e facile da mantenere è seguire almeno i singoli principi di inversione di responsabilità, sostituzione e dipendenza.

A cosa serve una libreria DI se puoi istanziare un oggetto da solo all'interno di un pacchetto e importarlo per iniettarlo tu stesso? La risposta scelta è giusta, poiché Java non ha sezioni procedurali (codice al di fuori delle classi), tutto ciò che va in noiosi xml di configurazione, quindi la necessità di una classe per istanziare e iniettare dipendenze su un pigro carica la moda in modo da non spazzare via la tua performance, mentre su python devi solo codificare le iniezioni nelle sezioni "procedurali" (codice al di fuori delle classi) del tuo codice

8

In realtà, è abbastanza facile scrivere codice sufficientemente pulito e compatto con DI (mi chiedo, sarà/rimani Pythonic allora, ma comunque :)) , ad esempio, perdo davvero questo modo di codificare:

def polite(name_str):
    return "dear " + name_str

def rude(name_str):
    return name_str + ", you, moron"

def greet(name_str, call=polite):
    print "Hello, " + call(name_str) + "!"

_

>>greet("Peter")
Hello, dear Peter!
>>greet("Jack", rude)
Hello, Jack, you, moron!

Sì, questo può essere visto come una semplice forma di parametrizzazione di funzioni/classi, ma fa il suo lavoro. Quindi, forse anche le batterie incluse in Python sono sufficienti qui.

Post scriptum Ho anche pubblicato un esempio più ampio di questo approccio ingenuo su Valutazione dinamica della logica booleana semplice in Python .

7
mlvljr

Appoggio la risposta "Jörg W Mittag": "L'implementazione Python di DI/IoC è così leggera da svanire completamente".

Per eseguire il backup di questa affermazione, dai un'occhiata al famoso esempio di Martin Fowler portato da Java a Python: Python: Design_Patterns: Inversion_of_Control

Come puoi vedere dal link sopra, un "Contenitore" in Python può essere scritto in 8 righe di codice:

class Container:
    def __init__(self, system_data):
        for component_name, component_class, component_args in system_data:
            if type(component_class) == types.ClassType:
                args = [self.__dict__[arg] for arg in component_args]
                self.__dict__[component_name] = component_class(*args)
            else:
                self.__dict__[component_name] = component_class
6
emilmont

Penso che a causa della natura dinamica di python le persone non vedono spesso la necessità di un altro framework dinamico. Quando una classe eredita dal nuovo 'oggetto' di stile puoi creare dinamicamente una nuova variabile ( https://wiki.python.org/moin/NewClassVsClassicClass ).

cioè In pitone semplice:

#application.py
class Application(object):
    def __init__(self):
        pass

#main.py
Application.postgres_connection = PostgresConnection()

#other.py
postgres_connection = Application.postgres_connection
db_data = postgres_connection.fetchone()

Tuttavia, dai un'occhiata a https://github.com/noodleflake/pyioc questo potrebbe essere quello che stai cercando.

vale a dire In piioc

from libs.service_locator import ServiceLocator

#main.py
ServiceLocator.register(PostgresConnection)

#other.py
postgres_connection = ServiceLocator.resolve(PostgresConnection)
db_data = postgres_connection.fetchone()
4
Martin Swanepoel

Il mio 2 centesimi è che nella maggior parte Python non ne hai bisogno e, anche se ne avevi bisogno, è probabile che molti Java (e violinisti incompetenti) che credono di essere sviluppatori) lo considerano qualcosa di brutto, solo perché è popolare in Java.

Un sistema IoC è effettivamente utile quando si hanno reti complesse di oggetti, in cui ogni oggetto può essere una dipendenza per molti altri e, a sua volta, essere esso stesso dipendente da altri oggetti. In tal caso, vorrai definire tutti questi oggetti una volta e avere un meccanismo per metterli insieme automaticamente, in base al maggior numero possibile di regole implicite. Se hai anche la configurazione che deve essere definita in modo semplice dall'utente/amministratore dell'applicazione, questo è un motivo in più per desiderare un sistema IoC in grado di leggere i suoi componenti da qualcosa come un semplice file XML (che sarebbe la configurazione).

La tipica Python è molto più semplice, solo un mucchio di script, senza un'architettura così complessa. Personalmente sono consapevole di cosa sia effettivamente un IoC (contrariamente a quelli che hanno scritto alcune risposte qui) e non ne ho mai sentito il bisogno nella mia esperienza limitata Python (anche io non uso Spring ovunque, non quando i vantaggi che offre non giustificano il suo sovraccarico di sviluppo).

Detto questo, ci sono Python situazioni in cui l'approccio IoC è effettivamente utile e, in effetti, ho letto qui che Django lo usa.

Lo stesso ragionamento sopra potrebbe essere applicato alla programmazione orientata agli aspetti nel mondo Java, con la differenza che il numero di casi in cui AOP è veramente utile è ancora più limitato.

3
zakmck