35 Stimmen

Warum geben diese Listenoperationen (Methoden: clear / extend / reverse / append / sort / remove) None zurück und nicht die resultierende Liste?

Ich habe festgestellt, dass viele Operationen auf Listen, die den Inhalt der Liste ändern, Folgendes zurückgeben None anstatt die Liste selbst zurückzugeben. Beispiele:

>>> mylist = ['a', 'b', 'c']
>>> empty = mylist.clear()
>>> restored = mylist.extend(range(3))
>>> backwards = mylist.reverse()
>>> with_four = mylist.append(4)
>>> in_order = mylist.sort()
>>> without_one = mylist.remove(1)
>>> mylist
[0, 2, 4]
>>> [empty, restored, backwards, with_four, in_order, without_one]
[None, None, None, None, None, None]

Welche Überlegungen stehen hinter dieser Entscheidung?

Mir scheint dies hinderlich zu sein, da es die "Verkettung" der Listenverarbeitung verhindert (z.B. mylist.reverse().append('a string')[:someLimit] ). Ich könnte mir vorstellen, dass "die Mächtigen" entschieden haben, dass das Listenverständnis das bessere Paradigma ist (eine berechtigte Meinung), und deshalb andere Methoden nicht fördern wollten - aber es scheint pervers, eine intuitive Methode zu verhindern, selbst wenn es bessere Alternativen gibt.


Diese Frage bezieht sich speziell auf Pythons <em>Entwurfsentscheidung </em>zur Rückkehr <code>None</code> von mutierenden Listenmethoden wie <code>.append</code> . Anfänger schreiben oft falschen Code, der erwartet <code>.append</code> (insbesondere), um die gleiche Liste zurückzugeben, die gerade geändert wurde.

Für die einfache Frage nach " <strong>wie </strong>an eine Liste anhängen?" (oder Fragen zur Fehlersuche, die auf dieses Problem hinauslaufen), siehe <a href="https://stackoverflow.com/questions/6339235">Warum funktioniert "x = x.append([i])" nicht in einer for-Schleife? </a>.

Um geänderte Versionen der Liste zu erhalten, siehe:

Das gleiche Problem gilt für einige Methoden anderer eingebauter Datentypen, z. B. <code>set.discard</code> (siehe <a href="https://stackoverflow.com/questions/22877032">Wie entfernt man ein bestimmtes Element aus einer Menge innerhalb einer Liste mit Hilfe des Listenverständnisses? </a>) und <code>dict.update</code> (siehe <a href="https://stackoverflow.com/questions/1452995/">Warum gibt ein python dict.update() das Objekt nicht zurück? </a>).

Die gleichen Überlegungen gelten für die Entwicklung eigener APIs. Siehe <a href="https://stackoverflow.com/questions/13062423">Ist es eine schlechte Idee, wenn In-Place-Operationen das Objekt zurückgeben? </a>.

43voto

Jon Clements Punkte 134241

Das allgemeine Designprinzip in Python ist für Funktionen, die ein Objekt an Ort und Stelle mutieren, um die None . Ich bin mir nicht sicher, ob ich mich für dieses Design entschieden hätte, aber im Grunde soll damit betont werden, dass kein neues Objekt zurückgegeben wird.

Guido van Rossum (unser Python-BDFL) erklärt die Wahl des Designs auf der Python-Dev Mailingliste :

Ich möchte noch einmal erklären, warum ich so hartnäckig darauf bestehe, dass sort() nicht 'self' zurückgeben sollte.

Das kommt von einem Kodierungsstil (der in verschiedenen anderen Sprachen verbreitet ist, ich glaube, vor allem Lisp schwelgt darin), bei dem eine Reihe von Seiteneffekten auf ein einzelnes Objekt auf diese Weise verkettet werden können:

x.compress().chop(y).sort(z)

was dasselbe wäre wie

x.compress()
x.chop(y)
x.sort(z)

Ich finde, dass die Verkettungsform eine Gefahr für die Lesbarkeit darstellt; sie verlangt, dass der dass der Leser mit den einzelnen Methoden vertraut sein muss. Die zweite Form macht deutlich, dass jeder dieser Aufrufe auf dasselbe Objekt wirkt, so dass man, auch wenn man die Klasse und ihre Methoden nicht sehr gut kennt nicht gut kennen, können Sie verstehen, dass der zweite und dritte Aufruf auf x angewendet werden (und dass alle Aufrufe wegen ihrer Nebeneffekte erfolgen), und nicht auf etwas anderes.

Ich würde die Verkettung gerne für Operationen reservieren, die neue Werte zurückgeben, wie Operationen zur Verarbeitung von Zeichenketten:

y = x.rstrip("\n").split(":").lower()

Es gibt ein paar Standardbibliotheksmodule, die die Verkettung von Seiteneffekt-Aufrufen fördern (pstat kommt mir in den Sinn). Es sollte keine neuen sein; pstat ist durch meinen Filter gerutscht, als es noch schwach war.

18voto

Tim Pietzcker Punkte 311448

Ich kann nicht für die Entwickler sprechen, aber ich finde dieses Verhalten sehr intuitiv.

Wenn eine Methode mit dem ursprünglichen Objekt arbeitet und es an Ort und Stelle verändert, gibt sie nichts zurück, weil es keine neuen Informationen gibt - Sie haben offensichtlich bereits einen Verweis auf das (nun veränderte) Objekt, warum also noch einmal zurückgeben?

Wenn eine Methode oder Funktion jedoch ein neues Objekt erzeugt, muss sie es natürlich auch zurückgeben.

Donc l.reverse() gibt nichts zurück (weil die Liste nun umgekehrt wurde, aber die identfier l verweist immer noch auf diese Liste), aber reversed(l) muss die neu erstellte Liste zurückgeben, da l verweist immer noch auf die alte, unveränderte Liste.

EDIT: Ich habe gerade von eine weitere Antwort dass dieses Prinzip als Trennung von Befehl und Abfrage .

3voto

icecrime Punkte 70619

Man könnte argumentieren, dass die Signatur selbst deutlich macht, dass die Funktion die Liste mutiert, anstatt eine neue zurückzugeben: Wenn die Funktion eine Liste zurückgeben würde, wäre ihr Verhalten viel weniger offensichtlich.

2voto

Karl Knechtel Punkte 55450

Wenn Sie hierher geschickt wurden, nachdem Sie um Hilfe bei der Korrektur Ihres Codes gebeten haben:

In der Zukunft, bitte versuchen selbst nach Problemen im Code zu suchen, indem Sie sorgfältiges Studium was passiert, wenn der Code ausgeführt wird. Anstatt aufzugeben, weil eine Fehlermeldung erscheint, überprüfen Sie das Ergebnis jeder Berechnung und sehen Sie, wo der Code anders funktioniert als erwartet.

Wenn Sie Code haben, der eine Methode wie .append o .sort auf einer Liste, werden Sie feststellen, dass die Rückgabewert es None , während die Liste geändert wird an Ort und Stelle . Studieren Sie das Beispiel sorgfältig:

>>> x = ['e', 'x', 'a', 'm', 'p', 'l', 'e']
>>> y = x.sort()
>>> print(y)
None
>>> print(x)
['a', 'e', 'e', 'l', 'm', 'p', 'x']

y hat die besondere None Wert, denn das ist der Wert, der zurückgegeben wurde. x geändert, weil die Sortierung an Ort und Stelle erfolgte.

Es funktioniert absichtlich so, so dass Code wie x.sort().reverse() Pausen. Lesen Sie die anderen Antworten, um zu verstehen, warum die Python-Entwickler das so wollten.

Um das Problem zu beheben

Denken Sie zunächst sorgfältig über die Absicht des Codes nach. Sollte x ändern? Brauchen wir wirklich eine separate y ?

Betrachten wir .sort Erstens. Wenn x ändern soll, dann rufen Sie x.sort() selbst, ohne das Ergebnis irgendwo zuzuordnen.

Wenn eine sortierte kopieren. benötigt wird, verwenden Sie stattdessen y = x.sorted() . Siehe Wie kann ich eine sortierte Kopie einer Liste erhalten? für Einzelheiten.

Für andere Methoden können wir modifizierte Kopien wie folgt erhalten:

.clear -> das hat keinen Sinn; eine "gelöschte Kopie" der Liste ist einfach eine leere Liste. Verwenden Sie einfach y = [] .

.append y .extend -> der einfachste Weg ist wahrscheinlich die Verwendung der + Betreiber. So fügen Sie mehrere Elemente aus einer Liste hinzu l verwenden y = x + l statt .extend . Um ein einzelnes Element hinzuzufügen e zuerst in eine Liste verpacken : y = x + [e] . Eine andere Möglichkeit in 3.5 und höher ist die Verwendung von Entpacken: y = [*x, *l] para .extend , y = [*x, e] para .append . Siehe auch Wie kann man der Methode list append() erlauben, die neue Liste zurückzugeben? para .append y Wie kann ich zwei Listen in Python miteinander verknüpfen? para .extend .

.reverse -> Überlegen Sie zunächst, ob eine tatsächliche Kopie erforderlich ist. Die eingebaute reversed gibt Ihnen eine Iterator die verwendet werden kann, um die Elemente in umgekehrter Reihenfolge zu durchlaufen. Um eine tatsächliche Kopie zu erstellen, übergeben Sie diesen Iterator einfach an list : y = list(reversed(x)) . Siehe Wie kann ich eine umgekehrte Kopie einer Liste erhalten (Vermeidung einer separaten Anweisung bei der Verkettung einer Methode nach .reverse)? für Einzelheiten.

.remove -> Ermitteln Sie den Index des Elements, das entfernt werden soll (mit .index ), und verwenden Sie dann Slicing, um die Elemente vor und nach diesem Punkt zu finden und sie zusammenzufügen. Als eine Funktion:

def without(a_list, value):
    index = a_list.index(value)
    return a_list[:index] + a_list[index+1:]

(Wir können übersetzen .pop in ähnlicher Weise eine modifizierte Kopie zu erstellen, obwohl natürlich .pop gibt tatsächlich ein Element aus der Liste zurück).

Siehe auch Eine schnelle Möglichkeit, eine Liste ohne ein bestimmtes Element in Python zurückzugeben .

(Wenn Sie vorhaben, die mehrere Elemente, sollten Sie unbedingt ein Listenverständnis verwenden (oder filter ) statt. Das wird viel einfacher sein als alle anderen Lösungen benötigt für das Entfernen von Elementen aus der Liste während der Iteration über sie. Auf diese Weise können auch natürlich gibt eine modifizierte Kopie).


Für jede der oben genannten Möglichkeiten können wir natürlich auch eine modifizierte Kopie erstellen, indem wir explizit Anfertigung einer Kopie und dann die In-Place-Methode auf die Kopie anwenden. Welcher Ansatz am elegantesten ist, hängt vom jeweiligen Kontext und vom persönlichen Geschmack ab.

-1voto

Oghli Punkte 2080

Wie wir wissen list in python ist ein veränderbares objekt und eines der merkmale eines veränderbaren objekts ist die fähigkeit, den zustand dieses objekts zu verändern, ohne dass der neue zustand einer variablen zugewiesen werden muss. wir sollten mehr über dieses thema demonstrieren, um die ursache dieses problems zu verstehen.

Ein Objekt, dessen interner Zustand geändert werden kann, ist änderbar . Auf der anderen Seite, unveränderlich lässt keine Änderungen an dem einmal erstellten Objekt zu. Die Veränderbarkeit von Objekten ist eine der Eigenschaften, die Python zu einem dynamisch typisierte Sprache .

Jedes Objekt in Python hat drei Attribute:

  • Identität - Dies bezieht sich auf die Adresse, auf die sich das Objekt im Speicher des Computers bezieht.
  • Typ - Dies bezieht sich auf die Art des Objekts, das erstellt wird. Zum Beispiel integer, list, string usw.
  • Wert - Dies bezieht sich auf den vom Objekt gespeicherten Wert. Zum Beispiel str = "a" .

Während ID y Typ nicht mehr geändert werden kann, können die Werte für veränderbare Objekte geändert werden.

Lassen Sie uns den folgenden Code Schritt für Schritt besprechen, um zu zeigen, was er in Python bedeutet:

Erstellen einer Liste, die Namen von Städten enthält

cities = ['London', 'New York', 'Chicago']

Drucken der Position des in der Speicheradresse erstellten Objekts im hexadezimalen Format

print(hex(id(cities)))

Output [1]: 0x1691d7de8c8

Hinzufügen einer neuen Stadt zur Liste der Städte

cities.append('Delhi')

Drucken der Elemente aus der Liste Städte, getrennt durch ein Komma

for city in cities:
    print(city, end=', ')

Output [2]: London, New York, Chicago, Delhi

Drucken der Position des in der Speicheradresse erstellten Objekts im hexadezimalen Format

print(hex(id(cities)))

Output [3]: 0x1691d7de8c8

Das obige Beispiel zeigt uns, dass wir in der Lage waren, den internen Zustand des Objekts zu ändern cities durch Hinzufügen einer weiteren Stadt 'Delhi' aber die Speicheradresse des Objekts hat sich nicht geändert. Dies bestätigt, dass wir kein neues Objekt erstellt haben, sondern dass dasselbe Objekt verändert oder mutiert wurde. Wir können also sagen, dass das Objekt, das ein Listentyp mit dem Namen einer Referenzvariablen ist cities es un MUTABLES OBJEKT .

Beim unveränderlichen Objekt kann der interne Zustand nicht verändert werden. Betrachten Sie zum Beispiel den folgenden Code und die damit verbundene Fehlermeldung, wenn Sie versuchen, den Wert eines Tupels bei index zu ändern 0

Erstellen eines Tupels mit Variablennamen foo

foo = (1, 2)

Ändern des Indexes 0 Wert von 1 a 3

foo[0] = 3

TypeError: 'tuple' object does not support item assignment 

Wir können aus den Beispielen schließen, warum ein veränderliches Objekt nichts zurückgeben sollte, wenn es Operationen ausführt, weil es den internen Zustand des Objekts direkt verändert und es keinen Sinn macht, ein neues verändertes Objekt zurückzugeben, im Gegensatz zu einem unveränderlichen Objekt, das ein neues Objekt mit dem veränderten Zustand zurückgeben sollte, nachdem es Operationen ausgeführt hat.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X