2013-06-20 12 views
8

Domanda: Quali sono i pro e i contro di scrivere uno __init__ che prende una raccolta direttamente come argomento, piuttosto che decomprimere il suo contenuto?Passare un argomento di raccolta senza decomprimere il suo contenuto

Contesto: Sto scrivendo una classe per elaborare i dati da diversi campi in una tabella di database. Eseguo un certo risultato di query di grandi dimensioni (~ 100 milioni di righe), passando una riga alla volta in una classe che esegue l'elaborazione. Ogni riga viene recuperata dal database come una tupla (o facoltativamente, come dizionario).

Discussione: Suppongo di essere interessato esattamente a tre campi, ma ciò che viene passato nella mia classe dipende dalla query e la query viene scritta dall'utente. L'approccio più semplice potrebbe essere uno dei seguenti:

class Direct: 
    def __init__(self, names): 
     self.names = names 

class Simple: 
    def __init__(self, names): 
     self.name1 = names[0] 
     self.name2 = names[1] 
     self.name3 = names[2] 

class Unpack: 
    def __init__(self, names): 
     self.name1, self.name2, self.name3 = names 

Ecco alcuni esempi di righe che potrebbero essere passato a una nuova istanza:

good = ('Simon', 'Marie', 'Kent')     # Exactly what we want 
bad1 = ('Simon', 'Marie', 'Kent', '10 Main St') # Extra field(s) behind 
bad2 = ('15', 'Simon', 'Marie', 'Kent')   # Extra field(s) in front 
bad3 = ('Simon', 'Marie')       # Forgot a field 

Di fronte a quanto sopra, Direct corre sempre (almeno fino a questo punto) ma è molto probabile che sia bacato (GIGO). Prende un argomento e lo assegna esattamente come dato, quindi questa potrebbe essere una tupla o una lista di qualsiasi dimensione, un valore Null, un riferimento di funzione, ecc. Questo è il modo più rapido e sporco che io possa pensare di inizializzare il oggetto, ma ritengo che la classe dovrebbe lamentarsi immediatamente quando fornisco i dati che non è chiaramente progettato per gestire.

Simple maniglie bad1 correttamente, è buggy quando somministrato bad2, e genera un errore quando somministrato bad3. È conveniente essere in grado di troncare efficacemente gli input da bad1 ma non vale gli errori che potrebbero provenire da bad2. Questo sembra ingenuo e incoerente.

Unpack sembra l'approccio più sicuro, perché genera un errore in tutti e tre i casi "cattivi". L'ultima cosa che vogliamo fare è riempire silenziosamente il nostro database di informazioni errate, giusto? Prende direttamente la tupla, ma mi consente di identificare i suoi contenuti come attributi distinti invece di costringermi a continuare a fare riferimento agli indici, e si lamenta se la tupla ha le dimensioni sbagliate.

D'altra parte, perché passare una collezione a tutti? Poiché so ho sempre voglia tre campi, posso definire __init__ di accettare esplicitamente tre argomenti, e decomprimere la collezione utilizzando il * -operator mentre passo al nuovo oggetto:

class Explicit: 
    def __init__(self, name1, name2, name3): 
     self.name1 = name1 
     self.name2 = name2 
     self.name3 = name3 

names = ('Guy', 'Rose', 'Deb') 
e = Explicit(*names) 

Le uniche differenze che vedo sono che la definizione di __init__ è un po 'più prolissa e noi eleviamo TypeError invece di ValueError quando la tupla ha le dimensioni sbagliate. Filosoficamente, sembra logico che se stiamo prendendo un gruppo di dati (una riga di una query) e esaminandone le parti (tre campi), dovremmo passare un gruppo di dati (la tupla) ma memorizzare le sue parti (le tre attributi). Quindi Unpack sarebbe meglio.

Se avessi voluto accettare un numero indeterminato di campi, piuttosto che sempre tre, ho ancora la possibilità di passare il tupla direttamente o utilizzare le liste di argomenti arbitrari (* args, ** kwargs) e * -operator disimballaggio. Quindi mi chiedo, questa è una decisione in stile completamente neutrale?

+1

Accetterei tre diversi argomenti che poi spostano questo problema al chiamante - una tupla è, dopo tutto, un numero fisso di elementi. – user2246674

risposta

6

Questa domanda è probabilmente la risposta migliore provando i diversi approcci e vedendo ciò che ha più senso per voi ed è il più facilmente comprensibile da altri leggendo il codice.

Ora che ho il vantaggio di una maggiore esperienza, mi chiedo, come faccio ad accedere a questi valori?

Quando accedo uno qualsiasi dei valori in questa collezione, sono io probabilità di essere utilizzando maggior parte o tutti i valori nella stessa subroutine o sezione di codice? In tal caso, l'approccio "diretto" è una buona scelta; è il più compatto e mi permette di pensare alla collezione come a una raccolta fino al punto che devo assolutamente prestare attenzione a cosa c'è dentro.

D'altra parte, se sto usando alcuni valori qui, alcuni valori lì, non voglio ricordare costantemente quale indice accedere o aggiungere verbosità sotto forma di tasti di dizionario quando potrei semplicemente riferirmi direttamente ai valori utilizzando attributi con nome separato. Probabilmente eviterei l'approccio "diretto" in questo caso, quindi devo solo pensare al fatto che esiste una raccolta quando la classe viene inizializzata per la prima volta.

Ciascuno degli approcci rimanenti comporta la suddivisione della raccolta in attributi diversi, e penso che il chiaro vincitore qui sia l'approccio "Esplicito". Gli approcci "Semplice" e "Disimballaggio" condividono una dipendenza nascosta sull'ordine della raccolta, senza offrire alcun vantaggio reale.

Problemi correlati