2013-01-31 6 views
34

Spesso mi trovo a sovrascrivere i metodi di una classe padre e non posso mai decidere se elencare esplicitamente i parametri forniti o utilizzare solo un costrutto *args, **kwargs. Una versione è migliore rispetto all'altra? Esiste una buona pratica? Quali (dis-) vantaggi mi mancano?Procedura consigliata per l'ereditarietà: * args, ** kwargs o specificando esplicitamente i parametri

class Parent(object): 

    def save(self, commit=True): 
     # ... 

class Explicit(Parent): 

    def save(self, commit=True): 
     super(Explicit, self).save(commit=commit) 
     # more logic 

class Blanket(Parent): 

    def save(self, *args, **kwargs): 
     super(Blanket, self).save(*args, **kwargs) 
     # more logic 

vantaggi percepiti variante esplicito

  • più esplicito (Zen di Python)
  • facile da afferrare
  • parametri funzionali facilmente accessibili

vantaggi percepiti coperta varia nt

  • più DRY
  • classe genitore è facilmente intercambiabile
  • cambiamento dei valori standard di metodo padre si propaga senza toccare altro codice
+2

Gran parte dipende esattamente da ciò che si sta sottoclasse ...Se c'è un'alta probabilità che tu aggiunga (o qualcun altro lo farà) kwarg aggiuntivi ai metodi della classe base, ha molto senso attenersi a '** kwargs'. Se questo non è probabile, la specificazione esplicita di kwargs è decisamente migliore da un punto di vista della leggibilità. –

+0

Beh, stai sicuramente buttando via le informazioni. * L'esplicito è meglio di implicito * e tutto il resto. – kojiro

+2

Avete due scelte: 1) nominare esplicitamente i parametri e * proibire * le modifiche alle firme nelle classi derivate (altrimenti goodluck con 'super' e l'ereditarietà multipla), o 2) Usare' ** kwargs' e consentire di cambiare la firma dei metodi . Quale dovrebbe essere usato probabilmente dipende dalla situazione. – Bakuriu

risposta

23

Liskov principio di sostituzione

In genere non si vuole che si firma del metodo di variare in tipi derivati. Questo può causare problemi se si desidera scambiare l'uso di tipi derivati. Questo viene spesso definito come Liskov Substitution Principle.

vantaggi di firme espliciti

Allo stesso tempo, non credo che sia corretto per tutti i metodi di avere una firma di *args, **kwargs. firme esplicito:

  • aiuto per documentare il metodo attraverso i buoni nomi degli argomenti
  • aiuto per documentare il metodo specificando che args sono necessari e che hanno valori di default
  • fornire la convalida implicita (mancanti args richiesti lanciano ovvie eccezioni)

argomenti variabili lunghezza e Coupling

Non confondere argomenti a lunghezza variabile per una buona pratica di accoppiamento. Ci dovrebbe essere una certa quantità di coesione tra una classe genitore e classi derivate altrimenti non sarebbero correlate tra loro. È normale che il codice correlato produca un accoppiamento che rifletta il livello di coesione.

luoghi di utilizzare argomenti di lunghezza variabile

uso di argomenti di lunghezza variabile non dovrebbe essere la vostra prima opzione. Dovrebbe essere usato quando hai una buona ragione come:

  • Definizione di un wrapper di funzione (cioè un decoratore).
  • Definizione di una funzione polimorfica parametrica.
  • Quando gli argomenti che è possibile eseguire in realtà sono completamente variabili (ad esempio una funzione di connessione DB generalizzata). Le funzioni di connessione DB di solito prendono uno connection string in molte forme diverse, sia in forma di singolo argo, sia in forma multi-arg. Ci sono anche diversi gruppi di opzioni per diversi database.
  • ...

stai facendo qualcosa di sbagliato?

Se si scopre che si creano spesso metodi che impiegano molti argomenti o metodi derivati ​​con firme diverse, si potrebbe avere un problema più grande nel modo in cui si sta organizzando il codice.

13

La mia scelta sarebbe:

class Child(Parent): 

    def save(self, commit=True, **kwargs): 
     super(Child, self).save(commit, **kwargs) 
     # more logic 

Evita l'accesso all'argomento di commit da *args e **kwargs e mantiene le cose al sicuro se cambia la firma di Parent:save (ad esempio aggiungendo un nuovo argomento predefinito).

Aggiornamento: In questo caso, avere gli argomenti * può causare problemi se un nuovo argomento posizionale viene aggiunto al genitore. Manterrei solo **kwargs e gestirò solo nuovi argomenti con valori predefiniti. Eviterebbe di propagare gli errori.

+1

Credo che questa sia la risposta migliore, perché risponde ai bisogni esposti dalla domanda. Vale a dire: mantenere gli argomenti espliciti rispetto alla possibilità di aggiungere argomenti al genitore in futuro. Nonostante la risposta fornita da @dietbuddha sia "accademicamente" corretta, non offre un'alternativa che potrebbe essere impiegata nella pratica, quando non si è sicuri di quale sarà il destino del proprio codice in futuro. Questa risposta risponde a questo. –

+0

La risposta fornita da Alex Martelli [in questo thread SO] (http://stackoverflow.com/questions/1098549/proper-way-to-use-kwargs-in-python) contiene ulteriori dettagli su questo argomento. –

+0

Il modo in cui lo scrivi implica che anche la firma del genitore abbia '** kwargs', il che non è il caso dell'esempio della domanda. –

4

Se si è certi che Bambino manterrà la firma, sicuramente l'approccio esplicito è preferibile, ma quando Bambino cambierà la firma Io personalmente preferisco utilizzare entrambi gli approcci:

class Parent(object): 
    def do_stuff(self, a, b): 
     # some logic 

class Child(Parent): 
    def do_stuff(self, c, *args, **kwargs): 
     super(Child, self).do_stuff(*args, **kwargs) 
     # some logic with c 

In questo modo, cambiamenti nella la firma è abbastanza leggibile in Child, mentre la firma originale è abbastanza leggibile in Parent.

A mio parere questo è anche il modo migliore quando si dispone di ereditarietà multipla, poiché chiamare super alcune volte è piuttosto disgustoso quando non si dispone di argomenti e kwargs.

Per quello che vale, questo è anche il modo preferito in alcune librerie e framework Python (Django, Tornado, Requests, Markdown, solo per citarne alcuni). Anche se non si dovrebbero basare le sue scelte su tali cose, sto semplicemente insinuando che questo approccio è abbastanza diffuso.

2

Preferisco gli argomenti espliciti perché il completamento automatico consente di vedere la firma del metodo della funzione mentre effettua la chiamata di funzione.

3

Non proprio una risposta, ma più nota a margine: Se davvero, vuole veramente per assicurarsi che i valori predefiniti per la classe padre vengono propagate alle classi figlie si può fare qualcosa di simile:

class Parent(object): 

    default_save_commit=True 
    def save(self, commit=default_save_commit): 
     # ... 

class Derived(Parent): 

    def save(self, commit=Parent.default_save_commit): 
     super(Derived, self).save(commit=commit) 

Tuttavia Devo ammettere che questo sembra abbastanza brutto e lo userei solo se sento che ne ho davvero bisogno.