Python mixin è un anti-pattern?

28

Sono pienamente consapevole del fatto che pylint e altri strumenti di analisi statica non sono onniscienti e talvolta il loro consiglio deve essere disobbedito. (Questo vale per varie classi di messaggi, non solo convention s.)

Se ho classi come

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, è forzato. Ecco un esempio migliore, se ti piace .

Credo che questo stile sia chiamato usando "mixins".

Come altri strumenti, pylint valuta questo codice a -21.67 / 10 , principalmente perché pensa more_methods e related_methods non hanno self o attributi otherfunc , stack , annd my_var perché senza eseguire il codice, a quanto pare non è possibile vedere related_methods e more_methods sono mescolati in implement_methods .

I compilatori e gli strumenti di analisi statica non possono sempre risolvere il problema di interruzione , ma ritengo che questo sia certamente un che guardando ciò che viene ereditato da implement_methods mostrerebbe che questo è perfettamente valido, e sarebbe una cosa molto semplice da fare.

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

In entrambi:

  1. Non riescono nemmeno provare a controllare l'ereditarietà o

  2. i mixin sono scoraggiati in Python idiomatico e leggibile

# 1 è ovviamente errato perché se chiedo a pylint di parlarmi di una mia classe che eredita unittest.TestCase che usa self.assertEqual , (qualcosa definito solo in unittest.TestCase ), non non lamentarsi.

I mixin sono unptoni o scoraggiati?

    
posta cat 10.03.2016 - 16:34
fonte

5 risposte

12

I mixin non sono un caso d'uso considerato dallo strumento. Ciò non significa che si tratti necessariamente di un caso di utilizzo errato, solo di uno non comune per Python.

Se i mixin sono utilizzati in modo appropriato in un caso particolare è un'altra questione. Il mixin anti-pattern che vedo più frequentemente sta usando mixin quando c'è sempre solo l'intenzione di essere una combinazione mista. Questo è solo un modo indiretto per nascondere una classe di Dio. Se non riesci a pensare ad un motivo adesso per scambiare o tralasciare uno dei mix, non dovrebbe essere un mixin.

    
risposta data 12.03.2016 - 01:01
fonte
12

Credo che i Mixins possano, assolutamente, essere Pythonic. Tuttavia, il modo idiomatico per silenziare il tuo linter - e migliorare la leggibilità del tuo Mixin - è quello di (1) definire metodi astratti che definiscono esplicitamente i metodi che i figli di Mixin devono implementare e (2) pre-definiscono None -valori valutati per i membri di 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()
    
risposta data 11.08.2017 - 01:43
fonte
8

Penso che i mixin possano essere buoni, ma penso anche che pylint abbia ragione in questo caso. Disclaimer: segue le informazioni basate su opinioni.

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

Il riferimento a membri di istanze non dichiarate (variabili e metodi) sembra un po 'strano.

L'approccio giusto dipende molto dal compito in corso.

Potrebbe trattarsi di un mixin con il relativo bit di stato mantenuto in esso.

Potrebbe essere una gerarchia di classi diverse in cui i metodi che attualmente si distribuiscono tramite un mixin sono in una classe base, mentre le differenze di implementazione di livello inferiore appartengono a sottoclassi. Questo sembra più adatto per il tuo caso con le operazioni di stack.

Potrebbe 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 i tuoi problemi di progettazione più grandi, quindi potremmo discutere se qualcosa è un anti-modello nel tuo caso.

    
risposta data 10.03.2016 - 17:31
fonte
4

Il linter non sa che usi una classe come mixin. Pylint sa che usi un mixin se aggiungi il suffisso 'mixin' o 'Mixin' alla fine del nome della classe, quindi il linter smette di lamentarsi.

Le mixine non sono cattive o buone di per sé, sono solo uno strumento. Li rendi un buon uso o un cattivo uso.

    
risposta data 04.03.2018 - 22:14
fonte
1

I mix sono ok?

Are Python mixins an anti-pattern?

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

Perché il tuo linter lamenta?

Pylint si lamenta ovviamente perché non sa da dove provengano otherfunc , stack e my_var .

Trivial?

Non vi è un motivo immediatamente apparente per separare questi due metodi in classi padre separate, nell'esempio nella domanda o nell'esempio più banale, 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

I costi di ciò che stai facendo è rendere il rumore del tuo linter report. 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 diversi spazi dei nomi che potrebbero rendere più difficile per i programmatori scoprire il significato di.

Conclusione

L'ereditarietà consente il riutilizzo del codice. Se riesci a riutilizzare il codice con i tuoi mix, ottimo, hanno creato per te un valore che probabilmente supera gli eventuali altri costi. Se non stai ricevendo il riutilizzo del codice / deduplicazione delle righe di codice, probabilmente non otterrai molto valore per i tuoi mix e, a quel punto, penso che il costo del rumore sia maggiore dei benefici.

    
risposta data 10.03.2016 - 17:52
fonte

Leggi altre domande sui tag