2009-08-13 12 views
16

io sono sempre infastidito da questo fatto:Tornando Nessuno o una tupla e spacchettamento

$ cat foo.py 
def foo(flag): 
    if flag: 
     return (1,2) 
    else: 
     return None 

first, second = foo(True) 
first, second = foo(False) 

$ python foo.py 
Traceback (most recent call last): 
    File "foo.py", line 8, in <module> 
    first, second = foo(False) 
TypeError: 'NoneType' object is not iterable 

Il fatto è che, al fine di decomprimere correttamente senza problemi che ho sia per prendere il TypeError o per avere qualcosa di simile

values = foo(False) 
if values is not None: 
    first, second = values 

Che è un po 'fastidioso. C'è un trucco per migliorare questa situazione (ad esempio per impostare sia il primo che il secondo su Nessuno senza dover tornare indietro (Nessuno, Nessuno)) o un suggerimento sulla migliore strategia di progettazione per casi come quello che presento? * variabili forse?

risposta

16

Beh, si potrebbe fare ...

first,second = foo(True) or (None,None) 
first,second = foo(False) or (None,None) 

ma per quanto ne so non c'è modo più semplice per espandere Nessuno per riempire la totalità di una tupla.

+0

ooooh ... questo non l'ho preso in considerazione. approccio molto "perlativo", ma intelligente. –

+0

Non sono d'accordo sul fatto che sia perl-ish. 'x o y' è un modo molto pitonico di dire' (x? x: y) 'o' (se x allora x else y) 'o' (se non x allora y) 'o comunque lo vuoi spiegare . –

+0

ok, ho appena escogitato un "cantare insieme" la frase perl tipica di "qualunque o die()". –

7

Non penso che ci sia un trucco. È possibile semplificare il codice chiamante a:

values = foo(False) 
if values: 
    first, second = values 

o anche:

values = foo(False) 
first, second = values or (first_default, second_default) 

dove first_default e second_default sono valori che ci si darà al primo e al secondo come default.

+0

Quale versione di Python è la seconda opzione funzionante? v = (1,); x, y = v o (v, 2) –

18

Non vedo cosa c'è di sbagliato nel restituire (Nessuno, Nessuno). È molto più pulito delle soluzioni suggerite qui che implicano molte più modifiche nel codice.

Inoltre, non ha senso che si desideri che None divida automaticamente in 2 variabili.

+1

Ero solo nel processo di pensare la stessa cosa. Sono d'accordo sul fatto che non ha senso: dal punto di vista concettuale, restituendo None w.r.t. una tupla di Nones è probabilmente diversa (ovviamente dipende dalla routine reale.Qui stiamo parlando di casi di simulazione) –

+2

+1 - assolutamente - è strano fare in modo che la funzione restituisca un mix di risultati simile a quello di whacko e debba occuparsene nel chiamante. –

+1

@Alex: beh, questo dipende. In generale, puoi avere casi di routine che dovrebbero restituire _x_ (con x = qualunque) o None se non trovato. Se sai che x è una tupla e una tupla di due oggetti, è un caso speciale, ma il caso _x_ o None se non trovato è ancora valido. Il fatto che tu lo spacchetti è ignaro della routine chiamata. –

2

È necessario prestare attenzione con lo stile x or y di soluzione. Funzionano, ma sono un po 'più ampi delle specifiche originali. In sostanza, cosa succede se foo(True) restituisce una tupla vuota ()? Finché sai che è OK trattarlo come (None, None), stai bene con le soluzioni fornite.

Se questo fosse uno scenario comune, probabilmente sarei scrivere una funzione di utilità come:

# needs a better name! :) 
def to_tup(t): 
    return t if t is not None else (None, None) 

first, second = to_tup(foo(True)) 
first, second = to_tup(foo(False)) 
1
def foo(flag): 
    return ((1,2) if flag else (None, None)) 
14

penso che ci sia un problema di astrazione.

Una funzione dovrebbe mantenere un certo livello di astrazione, che aiuta a ridurre la complessità del codice.
In questo caso, la funzione non sta mantenendo l'astrazione corretta, o il chiamante non la rispetta.

La funzione potrebbe essere qualcosa come get_point2d(); in questo caso, il livello dell'astrazione si trova sulla tupla e pertanto restituire None sarebbe un buon modo per segnalare un caso particolare (ad esempio un'entità non esistente). L'errore in questo caso sarebbe di aspettarsi due elementi, mentre in realtà l'unica cosa che sai è che la funzione restituisce un oggetto (con informazioni relative ad un punto 2d).

Ma potrebbe anche essere stato qualcosa come get_two_values_from_db(); in questo caso l'astrazione verrebbe interrotta restituendo None, poiché la funzione (come suggerisce il nome) dovrebbe restituire due valori e non uno!

In entrambi i casi, l'obiettivo principale di utilizzare una funzione, riducendo la complessità, è, almeno in parte, perso.

Si noti che questo problema non appare chiaramente con il nome originale; questo è anche il motivo per cui è sempre importante dare un buon nome alla funzione e ai metodi.

+1

ottimo commento. Vorrei poter fare +2 –

2

ne dite di questo:

$ cat foo.py 
def foo(flag): 
    if flag: 
     return (1,2) 
    else: 
     return (None,)*2 

first, second = foo(True) 
first, second = foo(False) 

Edit: Giusto per essere chiari, l'unico cambiamento è quello di sostituire return None con return (None,)*2. Sono estremamente sorpreso che nessun altro abbia pensato a questo. (O se lo sono, vorrei sapere perché non l'hanno usato.)

+3

Perché l'OP ha esplicitamente detto "senza dover tornare indietro (Nessuno, Nessuno)". 'return (None,) * 2' è lo stesso di' return (None, None) '. – mhawke

+0

Sei sicuro che sia quello che intendevo? Pensavo che l'OP volesse solo evitare * digitando * l'espressione lunga 'return None, None' tutto il tempo. Il mio modo risolve questo problema. –

+0

Beh, non lo risolve completamente, naturalmente, ma lo riduce e funziona sempre meglio per tuple più lunghe di una determinata dimensione. –

1

OK, vorrei solo tornare (Nessuno, Nessuno), ma finché siamo in whacko-land (heh), ecco un modo usando una sottoclasse di tuple. Nel caso contrario, non si restituisce None, ma si restituisce invece un contenitore vuoto, che sembra essere nello spirito delle cose. "Iteratore" del contenitore scompatta i valori Nessuno quando vuoto. Dimostra il protocollo iteratore comunque ...

testata utilizzando v2.5.2:

class Tuple(tuple): 
    def __iter__(self): 
     if self: 
      # If Tuple has contents, return normal tuple iterator... 
      return super(Tuple, self).__iter__() 
     else: 
      # Else return a bogus iterator that returns None twice... 
      class Nonerizer(object): 
       def __init__(self): 
        self.x=0 
       def __iter__(self): 
        return self 
       def next(self): 
        if self.x < 2: 
         self.x += 1 
         return None 
        else: 
         raise StopIteration 
      return Nonerizer() 


def foo(flag): 
    if flag: 
     return Tuple((1,2)) 
    else: 
     return Tuple() # It's not None, but it's an empty container. 

first, second = foo(True) 
print first, second 
first, second = foo(False) 
print first, second 

uscita è il desiderato:

1 2 
None None 
+0

standing ovation per il bel codice-fu :) –

+0

'Nonerizer()' può essere sostituito con 'itertools.repeat (None, 2)'. – augurar

+1

@augurar o semplicemente 'iter ((Nessuna, Nessuna))', rendendo inutile l'intero esercizio. –

0

ho trovato una soluzione per questo problema:

Return Nessuno o restituire un oggetto.

Tuttavia non si desidera scrivere una classe solo per restituire un oggetto. Per questo, è possibile utilizzare un nome tupla

Ti piace questa:

from collections import namedtuple 
def foo(flag): 
if flag: 
    return None 
else: 
    MyResult = namedtuple('MyResult',['a','b','c'] 
    return MyResult._make([1,2,3]) 

E poi:

result = foo(True) # result = True 
result = foo(False) # result = MyResult(a=1, b=2, c=3) 

e si ha accesso ai risultati come questo:

print result.a # 1 
print result.b # 2 
print result.C# 3