¿Son los mixins de Python un antipatrón?

34

Soy plenamente consciente de que pylintotras herramientas de análisis estático no lo saben todo y, a veces, sus consejos deben ser desobedecidos. (Esto se aplica a varias clases de mensajes, no solo conventions.)


Si tengo clases como

class related_methods():

    def a_method(self):
        self.stack.function(self.my_var)

class more_methods():

    def b_method(self):
        self.otherfunc()

class implement_methods(related_methods, more_methods):

    def __init__(self):
        self.stack  = some()
        self.my_var = other()

    def otherfunc(self):
        self.a_method()

Obviamente, eso es artificial. Aquí hay un mejor ejemplo, si lo desea .

Creo que este estilo se llama usando "mixins".

Al igual que otras herramientas, pylintcalifica este código -21.67 / 10, principalmente porque piensa more_methodsy related_methodsno tiene selfo atributos otherfunc, stacky my_varporque sin ejecutar el código, aparentemente no puede ver related_methodsy more_methodsestá mezclado implement_methods.

Los compiladores y las herramientas de análisis estático no siempre pueden resolver el problema de detención , pero creo que este es ciertamente un caso en el que ver lo que hereda implement_methodsdemostraría que esto es perfectamente válido, y eso sería algo muy fácil de hacer.

¿Por qué las herramientas de análisis estático rechazan este patrón OOP válido (creo)?

Ya sea:

  1. Ni siquiera intentan verificar la herencia o

  2. los mixins se desaniman en Python idiomático y legible


El # 1 es obviamente incorrecto porque si le pido pylintque me cuente sobre una clase mía que hereda los unittest.TestCaseusos self.assertEqual, (algo definido solo en unittest.TestCase), no se queja.

¿Son los mixins antipáticos o desanimados?

gato
fuente
1
¿Es esta una mala pregunta? ¿Está fuera de tema? ¿Es incontestable? ¿Soy estúpido? Las personas pueden DV pero no quieren ayudar a mejorarlo.
gato
1
Hay algunos que probablemente están cansados ​​de que la gente pregunte "¿es este un patrón (anti)" o "es esto (no) pitónico"?
9
@MichaelT Eso está bien. Pueden dejar de revisar preguntas entonces.
Katana314
2
@tac la gente votará negativamente por varias razones, pero nunca se les exige que digan por qué: el botón de votación negativa tiene una sugerencia de herramienta que dice: "esta pregunta no muestra ningún esfuerzo de investigación; no está claro o no es útil". respuesta aceptable 8 arriba y 1 abajo es bastante bueno, particularmente en una pregunta, donde los votos negativos son gratuitos. No dejes que te deprima. Aclamaciones.
Aaron Hall
@AaronHall Soy consciente de que las personas pueden hacer lo que quieran con sus votos, de hecho, les digo a los nuevos usuarios que se quejan de lo mismo.
gato

Respuestas:

16

Los mixins simplemente no son un caso de uso considerado por la herramienta. Eso no significa que sea necesariamente un mal caso de uso, solo uno poco común para Python.

Si los mixins se usan apropiadamente en un caso particular es otra cuestión. El anti-patrón de mixina que veo con más frecuencia es el uso de mixinas cuando solo se pretende que haya una combinación mixta. Esa es solo una forma indirecta de ocultar una clase de dios. Si no puede pensar en una razón en este momento para cambiar o dejar de lado uno de los mixins, no debería ser un mixin.

Karl Bielefeldt
fuente
Sí, es un objeto de Dios, lo admito. En este caso, diría que está bien que la CPU sea un objeto divino.
gato
15

Creo que los Mixins pueden ser absolutamente pitónicos. Sin embargo, la forma idiomática de silenciar su linter, y mejorar la legibilidad de sus Mixins, es tanto (1) definir métodos abstractos que definan explícitamente los métodos que los hijos de un Mixin deben implementar, como (2) predefinir None-valorados para los miembros de datos Mixin que los niños deben inicializar.

Aplicando este patrón a tu ejemplo:

from abc import ABC, abstractmethod


class related_methods():
    my_var = None
    stack = None

    def a_method(self):
        self.stack.function(self.my_var)


class more_methods(ABC):
    @abstractmethod
    def otherfunc(self):
        pass

    def b_method(self):
        self.otherfunc()


class implement_methods(related_methods, more_methods):
    def __init__(self):
        self.stack  = some()
        self.my_var = other()

    def otherfunc(self):
        self.a_method()
UltraBird
fuente
2
+1 por su respuesta, aunque la abstract other(self): passcosa cambia la confusión de la pelotilla por la mía
gato
Es aún más confuso sin el @abstractmethod ;-) Comencé con Java primero, así que esto está bien para mí.
Chris Huang-Leaver
9

Creo que los mixins pueden ser buenos, pero también creo que pylint es correcto en este caso. Descargo de responsabilidad: sigue el material basado en la opinión.

Una buena clase, mixins incluidos, tiene una clara responsabilidad. Idealmente, un mixin debería llevar todo el estado al que accederá, y la lógica para manejarlo. Por ejemplo, un buen mixin podría agregar un last_updatedcampo a una clase de modelo ORM, proporcionar lógica para establecerlo y métodos para buscar el registro más antiguo / más nuevo.

Hacer referencia a miembros de instancia no declarados (variables y métodos) parece un poco extraño.

El enfoque correcto depende mucho de la tarea en cuestión.

Puede ser una mezcla con el estado relevante que se mantiene en él.

Puede ser una jerarquía de clases diferente donde los métodos que distribuye actualmente a través de un mixin están en una clase base, mientras que las diferencias de implementación de nivel inferior pertenecen a subclases. Esto se ve más adecuado para su caso con las operaciones de pila.

Puede ser un decorador de clase que agrega uno o dos métodos; Esto generalmente tiene sentido cuando tiene que pasar algunos argumentos al decorador para afectar la generación del método.

Exponga su problema, explique sus preocupaciones de diseño más grandes, entonces podríamos discutir si algo es un antipatrón en su caso.

9000
fuente
6

El linter no sabe que usa una clase como mixin. Pylint es consciente de que usa un mixin si agrega el sufijo 'mixin' o 'Mixin' al final del nombre de la clase, luego el linter deja de quejarse.

linter_without_mixins linter2_with_mixin

Los mixins no son malos ni buenos per se, son solo una herramienta. Les haces un buen uso o un mal uso.

Dotoscat
fuente
2
esto se lee más como un comentario, vea Cómo responder
mosquito
2
esta es una respuesta valiosa porque no creo que alguna vez sabría sobre esa pista
cat
1

¿Están bien los mixins?

¿Son los mixins de Python un antipatrón?

Los mixins no se desaniman, son un buen caso de uso para la herencia múltiple.

¿Por qué se queja tu linter?

Pylint es, obviamente, quejándose porque no sabe dónde está el otherfunc, stacky my_var está viniendo.

¿Trivial?

No hay una razón aparente inmediata para que separe estos dos métodos en clases primarias separadas, ya sea en su ejemplo en la pregunta o en su ejemplo vinculado más trivial, que se muestra aquí.

201
202 class OpCore():
203     # nothing runs without these
204
205 class OpLogik(): 
206     # logic methods... 
207 
208 class OpString(): 
209     # string things
210 
211 
212 class Stack(OpCore, OpLogik, OpString): 
213 
214     "the mixin mixer of the above mixins" 

Costos de mixins y ruido.

El costo de lo que está haciendo es hacer que su linter informe de ruido. Ese ruido puede ocultar problemas más importantes con su código. Este es un costo importante para pesar. Otro costo es que está separando su código interrelacionado en diferentes espacios de nombres que pueden dificultar que los programadores descubran el significado de.

Conclusión

La herencia permite la reutilización del código. Si obtiene la reutilización del código con sus mixins, excelente, han creado un valor para usted que probablemente supera los posibles otros costos. Si no está obteniendo la reutilización / deduplicación de líneas de código, probablemente no esté obteniendo mucho valor por sus mixins, y en ese momento, creo que el costo del ruido es mayor que los beneficios.

Aaron Hall
fuente
Hay una buena razón para separarlos en el ejemplo vinculado y no trivial: es más fácil para mí organizar mi código cuando tengo una fracción de una clase completa que implementa matemáticas, otra que implementa cadenas, etc., y las mezcla. para conseguir la sopa Forth.
gato
"Otro costo es que está separando su código en diferentes espacios de nombres que pueden dificultar que los programadores descubran el significado de" - no, ese no es el caso en absoluto. Los espacios de nombres de clase hacen que sea más fácil saber qué hacen un grupo de métodos, y cuando alguien quiere usar la pila, debe llamar / implementar la Sopa Forth, no los ingredientes individuales
cat
@cat probablemente porque son artificiales y no se usan en un proyecto realmente grande, ninguno de los ejemplos realmente respalda sus puntos de uso de mixins. Ambos muestran solo una vez que se usa el mixin, en cuyo caso solo poner la implementación en un solo lugar es mejor estilo y organización. Ahora, si pudiera mostrar un caso en el que desea características comunes en todas las clases pero no desea una clase base común, allí es donde usaría un mixin. Que está al lado de tu pregunta de linting (UltraBird respondió bien). Pero esto responde a su pregunta en general sobre la aplicabilidad de la mezcla.
dlamblin