2009-09-22 11 views
7

(Titolo e contenuto aggiornati dopo aver letto la risposta di Alex)E 'Pythonic per una funzione per restituire un iterabile o non iterabile a seconda del suo ingresso?

In generale, credo che sia considerata una forma errata (non-Pythonic) per una funzione a volte restituire un oggetto iterabile e talvolta singolo a seconda dei suoi parametri.

Per esempio struct.unpack restituisce sempre una tupla anche se contiene solo un elemento.

Sto cercando di finalizzare l'API per un modulo e ho un paio di funzioni che possono prendere uno o più parametri (tramite *args) come questo:

a = s.read(10)  # reads 10 bits and returns a single item 
b, c = s.read(5, 5) # reads 5 bits twice and returns a list of two items. 

Quindi restituisce un singolo elemento, se c'è solo un parametro, altrimenti restituisce una lista. Ora penso che questo va bene e non è affatto confuso, ma ho il sospetto che gli altri possono essere in disaccordo.

Il caso d'uso più comune per queste funzioni potrebbe essere quella vogliono solo un singolo elemento restituito, in modo sempre restituendo una lista (o tupla) si sente male:

a, = s.read(10)  # Prone to bugs when people forget to unpack the object 
a = s.read(10)[0]  # Ugly and it's not clear only one item is being returned 

Un'altra opzione è quella di avere due funzioni:

a = s.read(10) 
b, c = s.read_list(5, 5) 

che è OK, ma ingombra l'API e richiede all'utente di ricordare il doppio di molte funzioni senza aggiungere alcun valore.

Quindi la mia domanda è: Qualche volta restituisce un oggetto iterabile e talvolta un singolo confuso e non-Pythonic? Se sì, qual è l'opzione migliore?


Aggiornamento: Penso che il consenso generale è che è molto cattivo solo restituire un iterabile volte. Credo che la migliore opzione per la maggior parte dei casi potrebbe essere quella di riportare sempre l'iterabile, anche se contenesse un solo elemento.

Detto questo, per il mio caso particolare, penso che andrò alla suddivisione in due funzioni (read(item)/readlist(*items)), il ragionamento è che penso che il caso di singolo articolo accadrà molto più spesso del caso di più oggetti , quindi rende più facile l'utilizzo e l'API cambia meno problematico per gli utenti.

Grazie a tutti.

risposta

12

Se avete intenzione di tornare iteratori a volte, e singoli oggetti sugli altri, direi che il ritorno sempre un iteratore, in modo da non avere pensarci.

Generalmente, si utilizzerà quella funzione in un contesto che si aspetta un iteratore, quindi se si dovesse verificare se un elenco per iterare o un oggetto per fare solo una volta il lavoro, allora è più facile solo restituire un iteratore e iterare sempre, anche se è una volta.

Se avete bisogno di fare qualcosa di diverso, se si ritorna un elemento, basta usare if len(var):.

Ricordate, la coerenza è un buon valore.

Mi chino a restituire un oggetto coerente, non necessariamente lo stesso tipo, ma se restituisco sempre un iterabile, restituisco sempre un iterable.

+2

+1. A volte essere una cosa e talvolta essere una lista di cose è di solito un errore. Python lo ha fatto per% -formatting e questo è ampiamente considerato un errore e una cattiva trappola. – bobince

+0

Avevo paura che la gente lo dicesse: è brutto avere una lista quando hai chiesto solo un singolo oggetto! –

+0

@Scot Griffiths: IMHO i potenziali bug causati dall'essere troppo intelligenti superano i problemi che una semplice variabile potrebbe causare. Perché non usare un metodo come'def read (a_tuple): 'invece di usare' * args'? – voyager

0

Nelle liste Python sono oggetti :) Quindi nessun tipo non corrispondente

+0

Abbastanza vero! Ho modificato la domanda per evitare confusione. –

1

L'unica situazione in cui lo farei è con una funzione o un metodo parametrizzato, in cui uno o più parametri forniti dal chiamante determinano il tipo restituito; per esempio, una funzione di "fabbrica" ​​che restituisce uno di una famiglia logicamente simile di oggetti:

newCharacter = characterFactory("human", "male", "warrior") 

Nel caso generale, in cui il chiamante non ottiene specificare, eviterei la "scatola di cioccolatini " comportamento. :)

+0

Nel mio caso particolare il numero di elementi restituiti equivale al numero di elementi dati nella chiamata di funzione, quindi non credo che l'utente sarà sorpreso da ciò che viene restituito. –

2

In generale, dovrei dire che restituire due tipi diversi è una cattiva pratica.

Immagina che il prossimo sviluppatore venga a leggere e manterrà il tuo codice. All'inizio lui/lei leggerà un metodo usando la tua funzione e penserà "Ah, read() restituisce un singolo oggetto."

Successivamente vedranno il codice che considera il risultato di read() come un elenco. Nella migliore delle ipotesi ciò semplicemente li confonderà e li costringerà a esaminare l'uso di read(). Nel peggiore dei casi potrebbero pensare che ci sia un bug nell'implementazione usando read() e tentare di risolverlo.

Infine, una volta compreso che read() restituisce due tipi possibili, dovranno chiedersi "c'è forse un terzo tipo di reso per cui devo essere pronto?"

Questo mi ricorda il detto: "Codice come se il prossimo ragazzo a mantenere il tuo codice è un maniaco omicida che sa dove vivi".

1

Potrebbe non essere una questione di "pitonico", ma piuttosto una questione di "buon design". Se hai restituito cose diverse E, nessuno deve eseguire controlli di tipo su di esse, quindi probabilmente è ok. Questo è il polimorfismo per te. OTOH, se il chiamante deve "perforare il velo", allora si ha un problema di progettazione, noto come violazione del Principio di sostituzione di Liskov. Pythonic o no, non è chiaramente un progetto OO, il che significa che sarà soggetto a bug e problemi di programmazione.

1

avrei letto (intero) e read_list (iterable).

In questo modo si potrebbe fare leggere (10) e tornare un unico risultato e read_list ([5, 5, 10, 5]) e tornare un elenco di risultati. Questo è sia più flessibile che esplicito.

2

Il ritorno di un singolo oggetto o di un iterabile di oggetti, a seconda degli argomenti, è decisamente difficile da gestire. Ma la domanda nel titolo è molto più generale e l'affermazione che la funzione evitano della libreria standard (o "in gran parte evitare") tornando tipi diversi in base all'argomento (s) è abbastanza corretta. Ci sono molti contro-esempi.

Le funzioni copy.copy e copy.deepcopy restituiscono lo stesso tipo del loro argomento, quindi ovviamente "restituiscono tipi diversi a seconda dell'argomento". "Ritorno stesso tipo di ingresso" è in realtà molto comune - si potrebbe classe qui, anche, il "fetch un oggetto di ritorno da un contenitore in cui è stato messo", anche se di solito questo è fatto con un metodo piuttosto che una funzione ;-) . Ma anche, nella stessa vena, prendere in considerazione itertools.repeat (una volta di eseguire iterazioni sul suo iteratore restituita), o, diciamo, filter ...:

>>> filter(lambda x: x>'f', 'zaplepidop') 
'zplpiop' 
>>> filter(lambda x: x>'f', list('zaplepidop')) 
['z', 'p', 'l', 'p', 'i', 'o', 'p'] 

filtrare una stringa restituisce una stringa, filtrando una lista restituisce una lista.

Ma aspettate, c'è di più! -) Funzioni pickle.loads e dei suoi amici (ad esempio nel modulo marshal oggetti & c) di ritorno di tipi interamente dipende dal valore che si sta passando come argomento. Così fa la funzione integrata eval (e allo stesso modo input, in Python 2. *). Questo è il secondo modello comune: costruire o ricostruire un oggetto come dettato dal valore dell'argomento (s), di una vasta (o anche ubicata) varietà di tipi possibili, e restituirlo.

so nulla di buono esempio della specifica anti-modello che hai potuto notare (e credo che sia un anti-modello, leggermente - non per qualsiasi motivo ad alta falutin', solo perché è fastidioso e scomodo da affrontare con;-). Nota che questi casi che ho esemplificato SONO comodo e pratico - questo è il vero discriminante del design nella maggior parte dei problemi di libreria standard! -)

+0

Hai ragione che la domanda è formulata in modo troppo generico, e in realtà si tratta solo di un problema intercambiabile e non intercambiabile. Immagino che se tu l'abbia definito un anti-pattern, allora è la campana a morto! -) –

Problemi correlati