¿Por qué IoC/DI no es común en Python?
design-patterns dependency-injection (14)
A diferencia de la naturaleza mecanografiada fuerte en Java. El comportamiento de escritura del pato de Python hace que sea tan fácil pasar objetos.
Los desarrolladores de Java se centran en la construcción de la estructura de clase y la relación entre los objetos, al tiempo que mantienen la flexibilidad. IoC es extremadamente importante para lograr esto.
Los desarrolladores de Python se están centrando en hacer el trabajo. Solo conectan las clases cuando lo necesitan. Ni siquiera tienen que preocuparse por el tipo de clase. Mientras pueda quackear, ¡es un pato! Esta naturaleza no deja espacio para IoC.
En Java, IoC / DI es una práctica muy común que se usa ampliamente en aplicaciones web, casi todos los frameworks disponibles y Java EE. Por otro lado, también hay muchas aplicaciones web grandes de Python, pero además de Zope (que he escuchado debería ser un código realmente horrible) IoC no parece ser muy común en el mundo de Python. (Por favor, nombra algunos ejemplos si crees que estoy equivocado).
Por supuesto, hay varios clones de frameworks Java IoC populares disponibles para Python, springpython por ejemplo. Pero ninguno de ellos parece acostumbrarse. Al menos, nunca me he topado con una aplicación web basada en Django o sqlalchemy + <insert your favorite wsgi toolkit here>
que utiliza algo así.
En mi opinión, IoC tiene ventajas razonables y facilitaría la sustitución del modelo de usuario predeterminado de django, por ejemplo, pero el uso extenso de las clases de interfaz y IoC en Python parece un poco extraño y no "pythonic". Pero tal vez alguien tenga una mejor explicación, por qué IoC no se usa ampliamente en Python.
Creo que debido a la naturaleza dinámica de Python, la gente no suele ver la necesidad de otro marco dinámico. Cuando una clase hereda del nuevo "objeto" de estilo, puede crear una nueva variable dinámicamente ( https://wiki.python.org/moin/NewClassVsClassicClass ).
es decir, en python llano:
#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()
Sin embargo, eche un vistazo a https://github.com/noodleflake/pyioc esto podría ser lo que está buscando.
es decir, en pyioc
from libs.service_locator import ServiceLocator
#main.py
ServiceLocator.register(PostgresConnection)
#other.py
postgres_connection = ServiceLocator.resolve(PostgresConnection)
db_data = postgres_connection.fetchone()
Django hace un gran uso de la inversión de control. Por ejemplo, el archivo de configuración selecciona el servidor de la base de datos, luego el marco proporciona las instancias de envoltura de la base de datos adecuadas para los clientes de la base de datos.
La diferencia es que Python tiene tipos de primera clase. Los tipos de datos, incluidas las clases, son en sí mismos objetos. Si desea algo para usar una clase en particular, simplemente nombre la clase. Por ejemplo:
if config_dbms_name == ''postgresql'':
import psycopg
self.database_interface = psycopg
elif config_dbms_name == ''mysql'':
...
El código posterior puede crear una interfaz de base de datos escribiendo:
my_db_connection = self.database_interface()
# Do stuff with database.
En lugar de las funciones de fábrica de boilerplate que Java y C ++ necesitan, Python lo hace con una o dos líneas de código ordinario. Esta es la fuerza de la programación funcional frente a la imperativa.
En mi opinión, cosas como la inyección de dependencia son síntomas de un marco rígido y demasiado complejo. Cuando el cuerpo principal del código se vuelve demasiado pesado para cambiarlo fácilmente, tiene que elegir partes pequeñas del mismo, definir interfaces para ellas y luego permitir que las personas cambien de comportamiento a través de los objetos que se conectan a esas interfaces. Eso está muy bien, pero es mejor evitar ese tipo de complejidad en primer lugar.
También es el síntoma de un lenguaje estáticamente tipado. Cuando la única herramienta que tienes para expresar la abstracción es la herencia, entonces eso es prácticamente lo que usas en todas partes. Dicho esto, C ++ es bastante similar, pero nunca captó la fascinación por los Constructores y las Interfaces en todos los sitios que los desarrolladores de Java hicieron. Es fácil volverse exuberante con el sueño de ser flexible y extensible al costo de escribir demasiado código genérico con poco beneficio real . Creo que es una cosa cultural.
Por lo general, creo que la gente de Python está acostumbrada a elegir la herramienta adecuada para el trabajo, que es un todo coherente y simple, en lugar de la única herramienta verdadera (con mil posibles complementos) que puede hacer cualquier cosa, pero ofrece una asombrosa variedad de posibles permutaciones de configuración. . Todavía hay partes intercambiables donde sea necesario, pero sin la necesidad del gran formalismo de definir interfaces fijas, debido a la flexibilidad de la tipificación del pato y la relativa simplicidad del lenguaje.
En realidad, es bastante fácil escribir código suficientemente limpio y compacto con DI (me pregunto si será / permanecer pitónico , pero de todos modos :)), por ejemplo, en realidad prefiero esta forma de codificación:
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í, esto puede verse como una simple forma de parametrizar funciones / clases, pero hace su trabajo. Por lo tanto, quizás las baterías incluidas por defecto de Python también sean suficientes aquí.
PD También he publicado un ejemplo más amplio de este enfoque ingenuo en la evaluación dinámica de lógica booleana simple en Python .
Estoy de acuerdo con @Jorg en el punto de que DI / IoC es posible, más fácil y aún más hermoso en Python. Lo que falta es los marcos que lo soportan, pero hay algunas excepciones. Para señalar un par de ejemplos que vienen a mi mente:
Los comentarios de Django le permiten conectar su propia clase de Comentarios con su lógica y formularios personalizados. [Más información]
Django le permite usar un objeto de perfil personalizado para adjuntarlo a su modelo de usuario. Esto no es completamente IoC pero es un buen enfoque. Personalmente me gustaría reemplazar el modelo de usuario de agujero como lo hace el marco de comentarios. [Más información]
IoC / DI es un concepto de diseño, pero desafortunadamente, a menudo se toma como un concepto que se aplica a ciertos idiomas (o sistemas de escritura). Me encantaría ver que los contenedores de inyección de dependencia se vuelvan mucho más populares en Python. Hay Spring, pero eso es un súper marco y parece ser un puerto directo de los conceptos de Java sin mucha consideración para "The Python Way".
Dadas las anotaciones en Python 3, decidí tener una grieta en un contenedor de inyección de dependencias completo, pero sencillo: https://github.com/zsims/dic . Se basa en algunos conceptos de un contenedor de inyección de dependencias .NET (que IMO es fantástico si alguna vez estás jugando en ese espacio), pero está mutado con los conceptos de Python.
IoC y DI son muy comunes en el código de Python maduro. Simplemente no necesita un marco para implementar DI gracias a la tipificación de pato.
El mejor ejemplo es cómo configurar una aplicación 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 utiliza DI en gran medida:
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
Déjame recordarte ( source ):
"Inyección de dependencia" es un término de 25 dólares para un concepto de 5 centavos. [...] La inyección de dependencia significa dar a un objeto sus variables de instancia. [...].
My 2cents es que en la mayoría de las aplicaciones de Python no lo necesita e, incluso si lo necesita, es probable que muchos de los que odian a Java (y los violinistas incompetentes que creen ser desarrolladores) lo consideren algo malo, simplemente porque es popular en Java. .
Un sistema de IoC es realmente útil cuando tiene redes complejas de objetos, donde cada objeto puede ser una dependencia de varios otros y, a su vez, ser un dependiente de otros objetos. En tal caso, querrá definir todos estos objetos una vez y tener un mecanismo para juntarlos automáticamente, basándose en tantas reglas implícitas como sea posible. Si el usuario / administrador de la aplicación también debe definir la configuración de forma sencilla, esa es una razón adicional para desear un sistema IoC que pueda leer sus componentes desde un archivo XML simple (que sería la configuración).
La aplicación típica de Python es mucho más simple, solo un montón de scripts, sin una arquitectura tan compleja. Personalmente, soy consciente de lo que realmente es un IoC (al contrario de los que escribieron ciertas respuestas aquí) y nunca sentí la necesidad de hacerlo en mi experiencia limitada de Python (tampoco uso Spring en todas partes, no cuando las ventajas da no justificar su sobrecarga de desarrollo).
Dicho esto, hay situaciones de Python en las que el enfoque IoC es realmente útil y, de hecho, leo aquí que Django lo usa.
El mismo razonamiento anterior podría aplicarse a la Programación Orientada a Aspectos en el mundo Java, con la diferencia de que la cantidad de casos en los que realmente vale la pena el AOP es aún más limitada.
No he usado Python en varios años, pero diría que tiene más que ver con que sea un lenguaje de tipo dinámico que cualquier otra cosa. Para un ejemplo simple, en Java, si quisiera probar que algo se escribió de manera estándar, podría usar DI y pasar cualquier PrintStream para capturar el texto que se está escribiendo y verificarlo. Sin embargo, cuando estoy trabajando en Ruby, puedo reemplazar dinámicamente el método de "poner" en STDOUT para realizar la verificación, dejando a DI completamente fuera de la imagen. Si la única razón por la que estoy creando una abstracción es para probar la clase que lo está utilizando (piense en las operaciones del sistema de archivos o el reloj en Java), entonces DI / IoC crea una complejidad innecesaria en la solución.
Parece que las personas realmente no entienden lo que significa la inyección de dependencia y la inversión de control.
La práctica de usar la inversión de control es tener clases o funciones que dependan de otras clases o funciones, pero en lugar de crear las instancias dentro de la clase de código de función, es mejor recibirlo como un parámetro, por lo que se puede archivar el acoplamiento suelto. Eso tiene muchos beneficios como mayor capacidad de prueba y para archivar el principio de sustitución de liskov.
Usted ve, al trabajar con interfaces e inyecciones, su código se puede mantener más, ya que puede cambiar el comportamiento fácilmente, ya que no tendrá que volver a escribir una sola línea de código (tal vez una o dos líneas en la configuración DI) de su la clase cambia su comportamiento, ya que las clases que implementan la interfaz que su clase está esperando pueden variar independientemente siempre que sigan la interfaz. Una de las mejores estrategias para mantener el código desacoplado y fácil de mantener es seguir al menos los principios de responsabilidad única, sustitución e inversión de dependencia.
¿Para qué sirve una biblioteca DI si puede crear una instancia de un objeto dentro de un paquete e importarlo para inyectarlo usted mismo? La respuesta elegida es correcta, ya que java no tiene secciones de procedimiento (código fuera de las clases), todo lo que se incluye en los xml de configuración aburridos, por lo tanto, la necesidad de una clase de instanciar e inyectar dependencias en un modo de carga perezosa para que no se escape. su rendimiento, mientras que en python simplemente codifica las inyecciones en las secciones "de procedimiento" (código fuera de las clases) de su código
Parte de esto es la forma en que funciona el sistema de módulos en Python. Puede obtener una especie de "singleton" gratis, solo importándolo de un módulo. Defina una instancia real de un objeto en un módulo, y luego cualquier código de cliente puede importarlo y obtener un objeto funcional, completamente construido / poblado.
Esto contrasta con Java, donde no se importan instancias reales de objetos. Esto significa que siempre tendrá que crear una instancia de ellos (o utilizar algún tipo de enfoque de estilo IoC / DI). Puede mitigar la molestia de tener que crear una instancia de todo usted mismo al tener métodos de fábrica estáticos (o clases de fábrica reales), pero aún así incurre en la sobrecarga de recursos de crear realmente nuevos métodos cada vez.
Realmente no creo que los DI / IoC sean tan infrecuentes en Python. Lo que no es común, sin embargo, son los marcos / contenedores DI / IoC.
Piénsalo: ¿qué hace un contenedor DI? Te permite
- unir componentes independientes en una aplicación completa ...
- ... en tiempo de ejecución.
Tenemos nombres para "cablear juntos" y "en tiempo de ejecución":
- scripting
- dinámica
Por lo tanto, un contenedor DI no es más que un intérprete para un lenguaje de scripting dinámico. En realidad, permítanme reformular eso: un contenedor Java / .NET DI típico no es más que un intérprete de mierda para un lenguaje de scripting dinámico realmente malo con una sintaxis basada en XML, a veces basada en errores.
Cuando programas en Python, ¿por qué querrías usar un lenguaje de guiones feo y malo cuando tienes un lenguaje de guiones hermoso y brillante a tu disposición? En realidad, esa es una pregunta más general: cuando programa en casi cualquier idioma, ¿por qué querría usar un lenguaje de guiones feo y malo cuando tiene a su disposición Jython y IronPython?
Entonces, para resumir: la práctica de DI / IoC es tan importante en Python como en Java, por exactamente las mismas razones. Sin embargo, la implementación de DI / IoC está integrada en el lenguaje y, a menudo, es tan ligera que se desvanece por completo.
(Aquí hay un breve resumen para una analogía: en ensamblaje, una llamada de subrutina es un asunto bastante importante: tiene que guardar sus variables locales y registros en la memoria, guardar su dirección de retorno en algún lugar, cambiar el puntero de instrucción a la subrutina que está llamando, haga arreglos para que de alguna manera salte de nuevo a su subrutina cuando esté terminada, coloque los argumentos en algún lugar donde el destinatario pueda encontrarlos, etc. IOW: en ensamblaje, "llamada de subrutina" es un patrón de diseño, y antes había idiomas como Fortran, que tenía llamadas de subrutinas incorporadas, la gente estaba construyendo sus propios "marcos de subrutinas". ¿Diría que las llamadas de subrutinas son "poco comunes" en Python, simplemente porque no usa marcos de subrutinas?
Por cierto: para ver un ejemplo de lo que parece llevar a DI a su conclusión lógica, eche un vistazo al Newspeak Programming Language de Gilad Bracha y sus escritos sobre el tema:
Respaldo la respuesta de "Jörg W Mittag": "La implementación Python de DI / IoC es tan liviana que se desvanece por completo".
Para hacer una copia de seguridad de esta declaración, eche un vistazo al famoso ejemplo de Martin Fowler portado desde Java a Python: Python:Design_Patterns:Inversion_of_Control
Como puede ver en el enlace anterior, un "Contenedor" en Python se puede escribir en 8 líneas de código:
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