Ist es Pythonic für eine Funktion, die abhängig von ihrer Eingabe eine iterierbare oder nicht iterierbare gibt?

7

(Titel und Inhalt wurden nach dem Lesen von Alex 'Antwort aktualisiert)

Im Allgemeinen glaube ich, dass es eine schlechte Form (unpythonisch) ist, dass eine Funktion manchmal einen iterierbaren und manchmal einen einzelnen Gegenstand zurückgibt, abhängig von seinen Parametern.

Zum Beispiel gibt struct.unpack immer ein Tupel zurück, auch wenn es nur ein Element enthält.

Ich versuche, die API für ein Modul zu finalisieren und ich habe ein paar Funktionen, die einen oder mehrere Parameter (via *args ) wie folgt annehmen können:

%Vor%

Es wird also ein einzelnes Element zurückgegeben, wenn nur ein Parameter vorhanden ist. Andernfalls wird eine Liste zurückgegeben. Jetzt denke ich, das ist in Ordnung und überhaupt nicht verwirrend, aber ich vermute, dass andere nicht zustimmen.

Der häufigste Anwendungsfall für diese Funktionen wäre, dass nur ein einzelnes Element zurückgegeben werden soll. Daher ist es immer falsch, eine Liste (oder ein Tupel) zurückzugeben:

%Vor%

Eine andere Möglichkeit besteht darin, zwei Funktionen zu haben:

%Vor%

Das ist OK, aber es füllt die API auf und erfordert, dass sich der Benutzer doppelt so viele Funktionen merkt, ohne einen Wert hinzuzufügen.

Meine Frage ist also: Gibt es manchmal einen iterierbaren und manchmal einen einzelnen Gegenstand, der verwirrend und unpythonisch ist? Wenn ja, was ist die beste Option?

Update : Ich denke, der allgemeine Konsens besteht darin, dass es sehr unartig ist, manchmal nur ein iterables zurückzugeben. Ich denke, dass die beste Option in den meisten Fällen darin besteht, das Iterable immer zurückzugeben, selbst wenn es nur ein Element enthält.

Nachdem ich das gesagt habe, denke ich, dass ich für meinen speziellen Fall die Aufteilung in zwei Funktionen ( read(item) / readlist(*items) ) anstreben werde, wobei ich denke, dass der Einzelfall viel häufiger passieren wird als der Mehrfach-Fall, so dass es einfacher zu verwenden und die API-Änderung für Benutzer weniger problematisch ist.

Danke allen.

    
Scott Griffiths 22.09.2009, 17:16
quelle

7 Antworten

12

Wenn Sie Iteratoren manchmal und einzelne Objekte auf anderen zurückgeben, würde ich sagen, dass Sie immer einen Iterator zurückgeben, so dass Sie nicht darüber nachdenken müssen.

Allgemein würden Sie diese Funktion in einem Kontext verwenden, der einen Iterator erwartet. Wenn Sie also überprüfen müssen, ob eine Liste iteriert werden soll oder ein Objekt nur einmal ausgeführt werden soll, ist es einfacher, einfach zurückzukehren ein Iterator und iterieren immer, auch wenn es einmalig ist.

Wenn Sie etwas anderes tun müssen, wenn Sie ein Element zurückgegeben haben, verwenden Sie einfach if len(var): .

Denken Sie daran, Konsistenz ist ein wertvolles Gut.

Ich lehne mich darauf ab, ein konsistentes Objekt zurückzugeben, nicht unbedingt den gleichen Typ, aber wenn ich immer ein iterables zurückgebe, gebe ich immer ein iterables zurück.

    
voyager 22.09.2009, 17:21
quelle
2

Im Allgemeinen würde ich sagen, dass die Rückgabe von zwei verschiedenen Arten eine schlechte Übung ist.

Stellen Sie sich vor, der nächste Entwickler würde Ihren Code lesen und pflegen. Zuerst liest er / sie eine Methode mit Ihrer Funktion und denkt "Ah, read () gibt einen einzelnen Gegenstand zurück."

Später sehen sie Code, der das Ergebnis von read () als Liste behandelt. Im besten Fall verwirren sie sie einfach und zwingen sie, die Benutzung von read () zu untersuchen. Im schlimmsten Fall denken sie vielleicht, dass es einen Fehler in der Implementierung gibt, indem sie read () verwenden und versuchen, sie zu beheben.

Wenn sie schließlich verstanden haben, dass read () zwei mögliche Typen zurückgibt, müssen sie sich selbst fragen: "Gibt es möglicherweise einen dritten Rückgabetyp, für den ich bereit sein muss?"

Das erinnert mich an das Sprichwort: "Code, als ob der nächste Typ, der deinen Code pflegt, ein mörderischer Wahnsinniger ist, der weiß, wo du lebst."

    
Michael Groner 22.09.2009 18:36
quelle
2

Je nach Argument ist es schwierig, ein einzelnes Objekt oder ein iterbares Objekt zurückzugeben. Aber die Frage in Ihrem Titel ist viel allgemeiner und die Behauptung, dass die Funktion der Standardbibliothek es vermeidet (oder "meistens meide"), verschiedene Typen basierend auf dem Argument (den Argumenten) zurückzugeben, ist völlig inkorrekt. Es gibt viele Gegenbeispiele.

Die Funktionen copy.copy und copy.deepcopy geben den gleichen Typ wie ihr Argument zurück, also geben sie natürlich unterschiedliche Typen zurück, abhängig vom Argument. "Gib den gleichen Typ wie die Eingabe zurück" ist eigentlich SEHR normal - du könntest hier auch klassen, "holen Sie ein Objekt aus einem Container, wo es hingelegt wurde", obwohl das normalerweise mit einer Methode und nicht mit einer Funktion gemacht wird ;-) . Betrachten Sie aber auch itertools.repeat (wenn Sie den zurückgegebenen Iterator iterieren) oder, sagen wir, filter ...:

%Vor%

Das Filtern einer Zeichenfolge gibt eine Zeichenfolge zurück, das Filtern einer Liste gibt eine Liste zurück.

Aber warten Sie, es gibt mehr! -) Funktionen pickle.loads und seine Freunde (zB im Modul marshal & amp; c) geben Objekte vom Typ vollständig zurück abhängig von dem übergebenen Wert ein Argument. Dasselbe gilt für die eingebaute Funktion eval (und ähnlich input in Python 2. *). Dies ist das zweite allgemeine Muster: konstruiere oder rekonstruiere ein Objekt, wie es durch den Wert des Arguments (der Argumente) vorgegeben ist, einer breiten (oder sogar überbrückten) Vielfalt möglicher Typen und bringe es zurück.

Ich kenne kein gutes Beispiel für das spezifische Anti-Muster, das Sie beobachtet haben (und ich glaube, es ist ein Anti-Muster, mild - nicht aus irgendeinem Grund, nur weil es lästig und unbequem ist, damit umzugehen ;-). Beachten Sie, dass diese Fälle, die ich beispielhaft dargestellt habe, handlich und praktisch sind - das ist das wirkliche Design, das in den meisten Standardbibliotheksproblemen diskriminierend ist! -)

    
Alex Martelli 23.09.2009 03:00
quelle
1

Die einzige Situation, in der ich dies tun würde, ist mit einer parametrisierten Funktion oder Methode, wobei einer oder mehrere der Parameter, die der Aufrufer gibt, den zurückgegebenen Typ bestimmt; Zum Beispiel eine "Fabrik" -Funktion, die eine logisch ähnliche Familie von Objekten zurückgibt:

%Vor%

Im allgemeinen Fall, wo der Aufrufer nicht spezifiziert werden muss, würde ich das Verhalten der "Pralinenschachtel" vermeiden. :)

    
Kevin Little 22.09.2009 18:10
quelle
1

Es darf nicht "pythonisch" sein, sondern "gutes Design". Wenn Sie verschiedene Dinge zurückgeben AND müssen sie keine check-checks an ihnen vornehmen, dann ist es wahrscheinlich in Ordnung. Das ist Polymorphismus für dich. OTOH, wenn der Anrufer den Schleier "durchbohren" muss, dann haben Sie ein Designproblem, das als Verletzung des Liskow-Substitutionsprinzips bekannt ist. Pythonic oder nicht, es ist eindeutig kein OO-Design, was bedeutet, dass es anfällig für Programmfehler und Programmierfehler ist.

    
Tim Ottinger 22.09.2009 19:35
quelle
1

Ich hätte gelesen ( ganze Zahl ) und read_list ( iterierbare ).

Auf diese Weise können Sie lesen (10) und ein einzelnes Ergebnis und eine read_list ([5, 5, 10, 5]) erhalten und eine Liste der Ergebnisse erhalten. Dies ist sowohl flexibler als auch expliziter.

    
Jason Christa 22.09.2009 20:21
quelle
0

In Python-Listen sind Objekte :) Also kein Typ stimmt nicht überein

    
drAlberT 22.09.2009 17:20
quelle

Tags und Links