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?
Accetterei tre diversi argomenti che poi spostano questo problema al chiamante - una tupla è, dopo tutto, un numero fisso di elementi. – user2246674