Warum sollten Funktionen immer den gleichen Typ zurückgeben?

Ich habe irgendwo gelesen, dass Funktionen immer nur einen Typ zurückgeben sollten, so dass der folgende Code als schlechter Code gilt:

def x(foo): if 'bar' in foo: return (foo, 'bar') return None 

Ich denke, die bessere Lösung wäre

 def x(foo): if 'bar' in foo: return (foo, 'bar') return () 

Wäre es nicht billiger Speicher weise, einen Nicht zurückzugeben, um dann ein neues leeres Tupel zu erstellen oder ist dieser Zeitunterschied zu klein, um auch bei größeren Projekten zu bemerken?

7 Solutions collect form web for “Warum sollten Funktionen immer den gleichen Typ zurückgeben?”

Warum sollen Funktionen Werte eines konsistenten Typs zurückgeben? Um die folgenden zwei Regeln zu erfüllen.

Regel 1 – eine Funktion hat einen "Typ" – Eingänge, die den Ausgängen zugeordnet sind. Es muss eine konsistente Art von Ergebnis zurückgeben, oder es ist keine Funktion. Es ist ein Chaos.

Mathematisch sagen wir eine Funktion, F, ist eine Abbildung von Domäne, D, zu Reichweite, R. F: D -> R Die Domäne und der Bereich bilden den "Typ" der Funktion. Die Eingabetypen und der Ergebnistyp sind für die Definition der Funktion ebenso wichtig wie der Name oder der Körper.

Regel 2 – Wenn Sie ein "Problem" haben oder kein richtiges Ergebnis zurückgeben können, heben Sie eine Ausnahme an.

 def x(foo): if 'bar' in foo: return (foo, 'bar') raise Exception( "oh, dear me." ) 

Sie können die oben genannten Regeln brechen, aber die Kosten für langfristige Wartbarkeit und Verständlichkeit sind astronomisch.

"Wäre es nicht billiger Gedächtnis, um einen Nichts zurückzugeben?" Falsche Frage.

Der Punkt ist nicht , das Gedächtnis auf Kosten des klaren, lesbaren, offensichtlichen Codes zu optimieren.

Es ist nicht so klar, dass eine Funktion immer Objekte eines begrenzten Typs zurückgeben muss, oder dass die Rücksendung von Nichts falsch ist. Zum Beispiel kann re.search ein _sre.SRE_Match Objekt oder ein NoneType Objekt zurückgeben:

 import re match=re.search('a','a') type(match) # <type '_sre.SRE_Match'> match=re.search('a','b') type(match) # <type 'NoneType'> 

Entworfen auf diese Weise können Sie für ein Spiel mit dem Idiom testen

 if match: # do xyz 

Wenn die Entwickler es nötig hätten, ein _sre.SRE_Match Objekt zurückzugeben, dann müsste das Idiom wechseln

 if match.group(1) is None: # do xyz 

Es würde keinen großen Gewinn geben, indem er re.search benötigt, um immer ein _sre.SRE_Match Objekt zurückzugeben.

Also denke ich, wie sie die Funktion entwerfen, muss von der Situation abhängen und insbesondere, wie Sie die Funktion nutzen wollen.

Beachten Sie auch, dass sowohl _sre.SRE_Match als auch NoneType Instanzen von Objekt sind, also im weitesten Sinne sind sie vom gleichen Typ. Also die Regel, dass "Funktionen immer nur einen Typ zurückgeben" ist eher sinnlos.

Having said that, gibt es eine schöne Einfachheit für Funktionen, die Objekte zurückgeben, die alle die gleichen Eigenschaften teilen. (Duck-Typisierung, nicht statisches Tippen, ist der Python-Weg!) Es kann Ihnen erlauben, die Funktionen miteinander zu verknüpfen: foo (bar (baz))) und wissen mit Sicherheit die Art des Objekts, das du am anderen Ende bekommst.

Dies kann Ihnen helfen, die Richtigkeit Ihres Codes zu überprüfen. Wenn man bedenkt, dass eine Funktion nur Objekte eines bestimmten begrenzten Typs zurückgibt, gibt es weniger Fälle zu überprüfen. "Foo immer wieder eine ganze Zahl, so dass solange eine ganze Zahl erwartet wird überall, wo ich foo, ich bin golden …"

Best Practice in dem, was eine Funktion zurückgeben sollte, variiert stark von Sprache zu Sprache und sogar zwischen verschiedenen Python-Projekten.

Für Python im Allgemeinen bin ich mit der Prämisse einverstanden, dass die Rücksendung von Nichts schlecht ist, wenn Ihre Funktion generell ein Iterable zurückgibt, weil das Iterieren ohne Prüfung unmöglich wird. Geben Sie einfach einen leeren iterable in diesem Fall, es wird immer noch false testen, wenn Sie Python Standard Wahrheitstest verwenden:

 ret_val = x() if ret_val: do_stuff(ret_val) 

Und noch erlauben Sie es zu übertreiben, ohne zu testen:

 for child in x(): do_other_stuff(child) 

Für Funktionen, die wahrscheinlich einen einzigen Wert zurückgeben werden, denke ich, dass ich nicht zurückkomme, ist einfach akzeptabel, nur dokumentieren, dass dies in deinem docstring passieren könnte.

Ich persönlich denke, es ist ganz gut für eine Funktion, um ein Tupel oder keine zurückzugeben. Allerdings sollte eine Funktion höchstens 2 verschiedene Typen zurückgeben und die zweite sollte eine Keine sein. Eine Funktion sollte niemals einen String und eine Liste zurückgeben.

Hier sind meine Gedanken über all das und ich werde versuchen zu erklären, warum ich denke, dass die akzeptierte Antwort meistens falsch ist.

Zuerst programming functions != mathematical functions . Am nächsten kann man mathematische Funktionen bekommen, wenn man funktionale Programmierung macht, aber selbst dann gibt es viele Beispiele, die anders sagen.

  • Funktionen müssen nicht eingegeben werden
  • Funktionen müssen nicht ausgegeben werden
  • Funktionen müssen nicht zur Eingabe des Ausgangssignals (wegen der vorherigen zwei Aufzählungspunkte)

Eine Funktion in der Programmierung ist einfach als Block des Gedächtnisses mit einem Start (der Eintrittspunkt der Funktion), ein Körper (leer oder sonst) und Ausgangspunkt (ein oder mehrere abhängig von der Implementierung) zu sehen, die alle dort sind Zum Zweck der Wiederverwendung von Code, den Sie geschrieben haben. Auch wenn man es nicht sieht, hat eine Funktion immer etwas zurück. Das ist eigentlich die Adresse der nächsten Aussage direkt nach dem Funktionsaufruf. Dies ist etwas, was Sie in all seiner Herrlichkeit sehen werden, wenn Sie einige wirklich Low-Level-Programmierung mit einer Assembly-Sprache (ich wage es Ihnen, die extra Meile gehen und einige Maschinen-Code von Hand wie Linus Torvalds, die jemals so oft erwähnt dies während Seine Seminare und Interviews: D). Darüber hinaus können Sie auch einige Eingaben und spucken auch einige Ausgabe. Deswegen

 def foo(): pass 

Ist ein absolut korrektes Stück Code.

Also warum würde die Rückkehr mehrerer Typen schlecht sein? Nun … es ist gar nicht, wenn du es nicht missbraucht hast. Dies ist natürlich eine Frage der schlechten Programmierkenntnisse und / oder nicht zu wissen, was die Sprache, die Sie verwenden können.

Wäre es nicht billiger Speicher weise, einen Nicht zurückzugeben, um dann ein neues leeres Tupel zu erstellen oder ist dieser Zeitunterschied zu klein, um auch bei größeren Projekten zu bemerken?

Soweit ich weiß – ja, ein NoneType Objekt zurückzukehren wäre viel billiger Speicher-weise. Hier ist ein kleines Experiment (zurückgegebene Werte sind Bytes):

 >> sys.getsizeof(None) 16 >> sys.getsizeof(()) 48 

Allerdings musst du auch den Code betrachten, der um den Funktionsaufruf herum ist und wie er behandelt, was auch immer deine Funktion zurückgibt. Überprüfst du nach NoneType ? Oder überprüfen Sie einfach, ob das zurückgegebene Tupel eine Länge von 0 hat? Diese Ausbreitung der zurückgegebenen Typen ( NoneType vs. leeres Tupel in deinem Fall) könnte tatsächlich langwieriger sein, um in deinem Gesicht zu handeln und zu sprengen. Vergessen Sie nicht – der Code selbst ist in den Speicher geladen, also wenn die Handhabung der NoneType erfordert zu viel Code (auch kleine Stücke von Code, aber in einer großen Menge) besser verlassen die leere Tupel, die auch vermeiden, Verwirrung in den Köpfen der Menschen mit Ihre Funktion und vergessen, dass es tatsächlich 2 Arten von Werten zurückgibt.

Apropos Rückkehr von mehreren Arten von Wert Dies ist der Teil, wo ich mit der akzeptierten Antwort (aber nur teilweise) zustimmen – Rückgabe eines einzigen Typs macht den Code mehr ohne Zweifel. Es ist viel einfacher, nur für Typ A dann A, B, C, etc. zu überprüfen.

Allerdings ist Python eine objektorientierte Sprache und als solche Vererbung, abstrakte Klassen etc. und alles, was Teil des gesamten OOP Shenanigans ist, kommt ins Spiel. Es kann so weit gehen, wie auch nur Klassen zu produzieren, die ich vor ein paar Monaten entdeckt habe und war erstaunt (noch nie gesehen, dass Sachen in C / C ++).

Nebennote: Sie können ein bisschen über Metaklassen und dynamische Klassen in diesem schönen Übersichtsartikel mit vielen Beispielen lesen.

Es gibt in der Tat mehrere Designmuster und Techniken, die nicht einmal ohne die sogenannten polymorphen Funktionen existieren würden. Im Folgenden gebe ich Ihnen zwei sehr populäre Themen (kann nicht einen besseren Weg finden, um beide in einem einzigen Begriff zusammenzufassen):

  • Duck typing – oft Teil der dynamischen Schreibsprachen, die Python ist ein Vertreter von
  • Factory- Methode Design-Muster – im Grunde ist es eine Funktion, die verschiedene Objekte auf der Grundlage der Eingabe, die es erhält zurückgibt.

Schließlich, ob Ihre Funktion eine oder mehrere Typen zurückgibt, basiert vollständig auf dem Problem, das Sie lösen müssen. Kann dieses polymorphe Verhalten missbraucht werden? Sicher, wie alles andere.

Wenn x so genannt wird

 foo, bar = x(foo) 

Rückgabe None würde zu einem

 TypeError: 'NoneType' object is not iterable 

Wenn 'bar' nicht in foo .

Beispiel

 def x(foo): if 'bar' in foo: return (foo, 'bar') return None foo, bar = x(["foo", "bar", "baz"]) print foo, bar foo, bar = x(["foo", "NOT THERE", "baz"]) print foo, bar 

Das führt zu:

 ['foo', 'bar', 'baz'] bar Traceback (most recent call last): File "f.py", line 9, in <module> foo, bar = x(["foo", "NOT THERE", "baz"]) TypeError: 'NoneType' object is not iterable 

Vorzeitige Optimierung ist die Wurzel allen Übels. Die winzigen Effizienzgewinne könnten zwar wichtig sein, aber nicht, bis Sie bewiesen haben, dass Sie sie brauchen.

Was auch immer Ihre Sprache: eine Funktion ist einmal definiert, aber neigt dazu, an einer beliebigen Anzahl von Orten verwendet werden. Mit einer konsistenten Rückgabetyp (nicht zu erwähnte dokumentierte Vor- und Nachbedingungen) bedeutet, dass Sie mehr Aufwand für die Funktion ausgeben müssen, aber Sie vereinfachen die Nutzung der Funktion enorm. Vermutung, ob die einmaligen Kosten dazu neigen, die wiederholten Einsparungen zu überwiegen …?

Python ist die beste Programmiersprache der Welt.