2009-08-13 7 views
16

Ich bin immer durch diese Tatsache verärgert:Keine Rückgabe oder ein Tupel und Auspacken

$ 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 

Tatsache ist, dass, um ohne Probleme richtig zu entpacken ich entweder die Typeerror zu fangen oder etwas wie

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

Was ist irgendwie nervig. Gibt es einen Trick, um diese Situation zu verbessern (zum Beispiel, um sowohl die erste als auch die zweite auf Keine zu stellen, ohne foo zurückzukehren (None, None)) oder einen Vorschlag über die beste Designstrategie für Fälle wie die, die ich präsentiere? * Variablen vielleicht?

Antwort

16

Nun, Sie tun könnte ...

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

aber soweit ich weiß, gibt es keinen einfacheren Weg, keine zu erweitern in der Gesamtheit eines Tupels zu füllen.

+0

ooooh ... das habe ich nicht berücksichtigt. sehr "perlish" Ansatz, aber clever. –

+0

Ich stimme nicht überein, dass es perl-ish ist. 'x oder y' ist eine sehr pythische Art zu sagen' (x? x: y) 'oder' (wenn x dann x sonst y) 'oder' (wenn nicht x dann y) 'oder wie auch immer du es erklären willst . –

+0

ok, gerade ausgearbeitet von einem "mitsingen" den "was auch immer oder stirb()" typischen Perlsatz. –

7

Ich glaube nicht, dass es einen Trick gibt.

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

oder sogar: Sie können Ihre Berufung Code vereinfachen

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

wo first_default und second_default sind Werte würden Sie auf erste und zweite als Standardwerte geben.

+0

Welche Python-Version ist die zweite Option? v = (1,); x, y = v oder (v, 2) –

18

Ich sehe nicht, was falsch ist mit der Rückkehr (None, None). Es ist viel sauberer als die hier vorgeschlagenen Lösungen, die viel mehr Änderungen in Ihrem Code beinhalten.

Es macht auch keinen Sinn, dass None automatisch in 2 Variablen aufgeteilt werden soll.

+1

Ich war gerade dabei, dasselbe zu denken. Ich stimme zu, dass es keinen Sinn ergibt: Vom konzeptionellen Standpunkt aus gesehen, die Rückkehr von None w.r.t. ein Tupel von Nones ist wahrscheinlich anders (natürlich hängt es von der realen Routine ab.Hier reden wir über Mock Cases) –

+2

+1 - absolut - es ist komisch, die Funktion so einen Whacko-Mix von Ergebnissen zurückgeben zu lassen und damit im Caller umgehen zu müssen. –

+1

@Alex: Nun, das kommt darauf an. Im Allgemeinen können Sie Fälle von Routinen haben, die _x_ zurückgeben sollen (mit x = was auch immer) oder None, wenn sie nicht gefunden werden. Wenn Sie wissen, dass x ein Tupel und ein Tupel aus zwei Objekten ist, handelt es sich um einen Sonderfall, aber der Fall _x_ oder None, falls nicht gefunden, ist immer noch gültig. Die Tatsache, dass Sie es auspacken, ist der aufgerufenen Routine nicht bewusst. –

2

Sie sollten vorsichtig sein mit der x or y Art der Lösung. Sie funktionieren, aber sie sind ein bisschen breiter als Ihre ursprüngliche Spezifikation. Was ist, wenn foo(True) ein leeres Tupel () zurückgibt? Solange Sie wissen, dass es in Ordnung ist, das als (None, None) zu behandeln, sind Sie gut mit den bereitgestellten Lösungen.

Wenn dies ein gängiges Szenario wäre, würde ich wahrscheinlich eine Nutzenfunktion schreiben wie:

# 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

Ich denke, es ist ein Problem der Abstraktion.

Eine Funktion sollte eine gewisse Abstraktionsebene beibehalten, um die Komplexität des Codes zu reduzieren.
In diesem Fall behält entweder die Funktion nicht die richtige Abstraktion bei, der Aufrufer respektiert sie nicht.

Die Funktion könnte etwas wie get_point2d() gewesen sein; In diesem Fall befindet sich die Ebene der Abstraktion auf dem Tupel, und daher wäre die Rückgabe von None eine gute Möglichkeit, einen bestimmten Fall (z. B. eine nicht existierende Entität) zu signalisieren. Der Fehler würde in diesem Fall zwei Elemente erwarten, während das einzige, was Sie wissen, ist, dass die Funktion ein Objekt zurückgibt (mit Informationen, die sich auf einen 2-Punkt beziehen).

Aber es könnte auch etwas wie get_two_values_from_db() gewesen sein; In diesem Fall würde die Abstraktion durch die Rückgabe von None unterbrochen, da die Funktion (wie der Name vermuten lässt) zwei Werte zurückgeben soll und nicht einen!

In beiden Fällen ist das Hauptziel der Verwendung einer Funktion - Reduzierung der Komplexität - zumindest teilweise verloren.

Beachten Sie, dass dieses Problem nicht eindeutig mit dem ursprünglichen Namen angezeigt wird; deshalb ist es auch immer wichtig, Funktionen und Methoden gut zu benennen.

+1

großer Kommentar. Ich wünschte, ich könnte +2 tun –

2

Wie wäre es damit:

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

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

Edit: Nur klar zu sein, ist die einzige Änderung return None mit return (None,)*2 zu ersetzen. Ich bin sehr überrascht, dass sonst niemand daran gedacht hat. (Oder wenn sie haben, würde ich gerne wissen, warum sie es nicht verwendet haben.)

+3

Weil das OP ausdrücklich sagte "ohne foo zurückzukehren (None, None)". 'return (None,) * 2' ist dasselbe wie' return (None, None) '. – mhawke

+0

Sind Sie sicher, dass das gemeint war? Ich dachte, die OP wollte nur vermeiden, den langen Ausdruck "Return None, None" die ganze Zeit zu tippen. Mein Weg löst dieses Problem. –

+0

Nun, es löst es natürlich nicht vollständig, aber reduziert es und funktioniert immer besser für längere Tupel einer bestimmten Größe. –

1

OK, ich würde nur zurückkehren (None, None), aber solange wir in Whacko-Land (heh) sind, Hier ist eine Möglichkeit, eine Unterklasse von Tupel zu verwenden. Im anderen Fall geben Sie keine zurück, sondern geben stattdessen einen leeren Container zurück, der im Geiste der Dinge zu sein scheint. Der "Iterator" des Containers entpackt im leeren Zustand keine Werte. trotzdem zeigt das Iterator-Protokoll ...

Getestet mit 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 

Ausgang wird die gewünschte:

1 2 
None None 
+0

Standing Ovation für den netten Code-fu :) –

+0

'Nonerizer()' kann durch 'itertools.repeat (None, 2)' ersetzt werden. – augurar

+1

@augurar oder nur 'iter ((None, None))', macht die ganze Übung sinnlos. –

0

fand ich eine Lösung für dieses Problem:

Return Keine oder gibt ein Objekt zurück.

Sie möchten jedoch nicht nur eine Klasse schreiben müssen, um ein Objekt zurückzugeben. Dazu können Sie eine namens Tupel

wie folgt verwenden:

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

Und dann:

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

Und Sie haben Zugriff auf die Ergebnisse wie folgt aus:

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