I mixin Python sono un anti-pattern?


34

Sono pienamente consapevole del fatto che pylinte altri strumenti di analisi statica non sono onniscienti, e talvolta i loro consigli devono essere disobbediti. (Questo vale per varie classi di messaggi, non solo per conventions.)


Se ho lezioni simili

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()

Ovviamente, è inventato. Ecco un esempio migliore, se vuoi .

Credo che questo stile sia chiamato "mixin".

Come altri strumenti, pylinti tassi di questo codice -21.67 / 10, in primo luogo perché pensa more_methodse related_methodsnon hanno selfo attributi otherfunc, stack, gustato my_var, perché senza correre il codice, a quanto pare non può vedere related_methodse more_methodssi mescolano-in a implement_methods.

I compilatori e gli strumenti di analisi statica non possono sempre risolvere il problema dell'arresto , ma penso che questo sia certamente un caso in cui guardare ciò che è ereditato implement_methodsdimostrerebbe che questo è perfettamente valido e che sarebbe una cosa molto semplice da fare.

Perché gli strumenti di analisi statica rifiutano questo modello OOP valido (credo)?

O:

  1. Non provano nemmeno a controllare l'eredità o

  2. i mixin sono scoraggiati in Python idiomatico e leggibile


# 1 è ovviamente errato perché se chiedo pylintdi parlarmi di una mia classe che eredita unittest.TestCasequell'uso self.assertEqual(qualcosa definito solo in unittest.TestCase), non si lamenta.

I mixin non sono ritonici o scoraggiati?


1
Questa è una brutta domanda? È fuori tema? è senza risposta? Sono stupido? Le persone possono DV ma non vogliono contribuire a migliorarlo.
gatto

1
Ci sono alcuni che sono probabilmente stanchi di chiedere alla gente "è questo un (anti-) modello" o "è questo (non) pitonico".

9
@MichaelT Va bene. Quindi possono smettere di rivedere le domande.
Katana314

2
Le persone di @tac effettueranno il downgrade per vari motivi, ma non sono mai tenuti a dire perché - il pulsante di downvote ha un suggerimento che dice "questa domanda non mostra alcuno sforzo di ricerca; è poco chiara o non utile" - un vago, se risposta accettabile. 8 up e 1 down sono in realtà abbastanza buoni, in particolare su una domanda, in cui i downgrade sono gratuiti. Non lasciarti abbattere. Saluti.
Aaron Hall,

@AaronHall Sono consapevole che alle persone è permesso fare ciò che vogliono con i loro voti, anzi lo dico ai nuovi utenti che si lamentano della stessa cosa.
gatto

Risposte:


16

I mixin non sono solo un caso d'uso considerato dallo strumento. Ciò non significa che sia necessariamente un caso di cattivo utilizzo, solo un caso insolito per Python.

Se i mixin siano usati in modo appropriato in un caso particolare è un'altra questione. L'anti-pattern di mixin che vedo più frequentemente sta usando i mixin quando c'è sempre e solo una combinazione. Questo è solo un modo per nascondere una classe divina. Se non riesci a pensare a una ragione in questo momento per scambiare o tralasciare uno dei mixin, non dovrebbe essere un mixin.


si, è un oggetto divino, lo ammetto. In questo caso, direi che va bene che la CPU sia un oggetto divino.
gatto

15

Credo che i Mixin possano essere assolutamente Pythonic. Tuttavia, il modo idiomatico di mettere a tacere la tua linter - e migliorare la leggibilità dei tuoi Mixin - è sia di (1) definire metodi astratti che definiscono esplicitamente i metodi che i figli di un Mixin sono tenuti ad attuare, sia di (2) pre-definire None-valutato campi per i membri dei dati Mixin che i bambini devono inizializzare.

Applicando questo modello al tuo esempio:

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()

2
+1 per la tua risposta, anche se la abstract other(self): passcosa scambia la confusione della linter con la mia
cat

È ancora più confuso senza il @abstractmethod ;-) Ho iniziato con Java prima, quindi questo va bene per me.
Chris Huang-Leaver, il

9

Penso che i mixin possano essere buoni, ma penso anche che il pilone sia giusto in questo caso. Disclaimer: segue materiale basato sull'opinione.

Una buona classe, compresi i mixin, ha una chiara responsabilità. Idealmente, un mixin dovrebbe portare tutto lo stato a cui accederà e la logica per gestirlo. Ad esempio, un buon mixin potrebbe aggiungere un last_updatedcampo a una classe di modello ORM, fornire la logica per impostarlo e metodi per cercare il record più vecchio / più recente.

Fare riferimento a membri di istanza non dichiarati (variabili e metodi) sembra un po 'strano.

L'approccio giusto dipende molto dal compito da svolgere.

Potrebbe essere un mixin con il bit di stato rilevante contenuto in esso.

Potrebbe trattarsi di una gerarchia di classi diversa in cui i metodi attualmente distribuiti tramite un mixin si trovano in una classe di base, mentre le differenze di implementazione di livello inferiore appartengono alle sottoclassi. Questo sembra più adatto al tuo caso con le operazioni dello stack.

Può essere un decoratore di classe che aggiunge un metodo o due; questo di solito ha senso quando devi passare alcuni argomenti al decoratore per influenzare la generazione del metodo.

Indica il tuo problema, spiega le tue maggiori preoccupazioni di progettazione, quindi potremmo discutere se qualcosa è un anti-modello nel tuo caso.


6

La linter non è a conoscenza del fatto che usi una classe come mixin. Pylint è consapevole del fatto che usi un mixin se aggiungi il suffisso 'mixin' o 'Mixin' alla fine del nome della classe, quindi la linter smette di lamentarsi.

linter_without_mixins linter2_with_mixin

I mixin non sono cattivi o buoni di per sé, sono solo uno strumento. Li fai un buon uso o un cattivo uso.


2
questo sembra più un commento, vedi Come rispondere
moscerino

2
questa è una risposta preziosa perché non credo che avrei mai saputo di quel suggerimento altrimenti
cat

1

I mixin sono ok?

I mixin Python sono un anti-pattern?

I mixin non sono scoraggiati: sono un buon caso d'uso per l'ereditarietà multipla.

Perché la tua linter si lamenta?

Pylint si sta ovviamente lamentando perché non sa da dove otherfuncviene stack, e my_var proviene.

Banale?

Non vi è alcuna ragione immediatamente apparente per separare questi due metodi in classi genitore separate, sia nel tuo esempio nella domanda, sia nel tuo esempio più banale collegato, mostrato qui.

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" 

Costi di mixin e rumore

Il costo di ciò che stai facendo è rendere rumoroso il tuo linter. Quel rumore potrebbe oscurare problemi più importanti con il tuo codice. Questo è un costo importante da pesare. Un altro costo è che stai separando il tuo codice correlato in spazi di nomi diversi che potrebbe rendere più difficile per i programmatori scoprire il significato di.

Conclusione

L'ereditarietà consente il riutilizzo del codice. Se ottieni il riutilizzo del codice con i tuoi mixin, fantastico, hanno creato valore per te che probabilmente supera i possibili altri costi. Se non stai ottenendo il riutilizzo / deduplicazione del codice di righe di codice, probabilmente non stai ottenendo molto valore per i tuoi mixin e, a quel punto, penso che il costo del rumore sia maggiore dei vantaggi.


C'è una buona ragione per separarli nell'esempio collegato e non banale: è più facile per me organizzare il mio codice quando ho una frazione di una classe completa che implementa la matematica, un'altra che implementa le stringhe e così via, e le mescoli insieme per ottenere Forth Soup.
cat

"Un altro costo è che stai separando il tuo codice in diversi spazi dei nomi che potrebbe rendere più difficile per i programmatori scoprire il significato di" - no, non è affatto così. Gli spazi dei nomi delle classi rendono più facile dire cosa fa un gruppo di metodi e quando qualcuno vuole usare lo stack, dovrebbe chiamare / implementare la Forth Soup, non i singoli ingredienti
cat

@cat probabilmente perché sono inventati e non utilizzati in un progetto di grandi dimensioni, nessuno dei due esempi supporta realmente i tuoi punti di utilizzo dei mixin. Entrambi mostrano solo una volta che viene utilizzato il mixin, nel qual caso mettere l'implementazione in un unico posto è uno stile e un'organizzazione migliori. Ora, se potessi mostrare un caso in cui desideri funzionalità comuni tra le classi ma non desideri una classe base comune, è qui che utilizzeresti un mixin. Che è accanto alla tua domanda sui pelucchi (UltraBird ha risposto così bene). Ma questo risponde alla tua domanda in generale sull'applicabilità del mixin.
dlamblin,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.