Was ist die pythonische Methode, um Standardparameter zu vermeiden, bei denen es sich um leere Listen handelt?

117

Manchmal scheint es natürlich, einen Standardparameter zu haben, der eine leere Liste ist. Doch Python gibt ein unerwartetes Verhalten in diesen Situationen .

Wenn ich zum Beispiel eine Funktion habe:

def my_func(working_list = []):
    working_list.append("a")
    print(working_list)

Das erste Mal, wenn es als Standard aufgerufen wird, funktioniert, aber Anrufe danach aktualisieren die vorhandene Liste (mit einem "a" pro Anruf) und drucken die aktualisierte Version.

Was ist also der pythonische Weg, um das gewünschte Verhalten zu erreichen (eine neue Liste bei jedem Anruf)?

John Mulder
quelle
17
Wenn jemand daran interessiert ist, warum dies passiert, besuchen Sie effbot.org/zone/default-values.htm
Ryan Haining
Das gleiche Verhalten tritt für Sets auf, obwohl Sie ein etwas komplizierteres Beispiel benötigen, damit es als Fehler angezeigt wird.
Abeboparebop
Lassen Sie mich ausdrücklich darauf hinweisen, dass dies das gewünschte Verhalten ist, wenn Links sterben. Standardvariablen werden bei der Funktionsdefinition (die beim ersten Aufruf auftritt) ausgewertet und NICHT bei jedem Aufruf der Funktion. Wenn Sie also ein veränderbares Standardargument mutieren, kann jeder nachfolgende Funktionsaufruf nur das mutierte Objekt verwenden.
Moritz

Antworten:

150
def my_func(working_list=None):
    if working_list is None: 
        working_list = []

    working_list.append("a")
    print(working_list)

In den Dokumenten wird angegeben, dass Sie diese Noneals Standard verwenden und im Hauptteil der Funktion explizit testen sollten .

HenryR
quelle
1
Ist es besser zu sagen: wenn Arbeitsliste == Keine: oder wenn Arbeitsliste: ??
John Mulder
2
Dies ist der bevorzugte Weg, um es in Python zu tun, auch wenn ich es nicht mag, da es hässlich ist. Ich würde sagen, die beste Vorgehensweise wäre "wenn die Arbeitsliste Keine ist".
E-Satis
21
Der bevorzugte Weg in diesem Beispiel ist zu sagen: Wenn die Arbeitsliste Keine ist. Der Anrufer hat möglicherweise ein leeres listenähnliches Objekt mit einem benutzerdefinierten Anhang verwendet.
tzot
5
Mohit Ranka: Beachten Sie, dass nicht working_list True ist, wenn seine Länge 0 ist. Dies führt zu einem inkonsistenten Verhalten: Wenn die Funktionen eine Liste mit einem Element darin erhalten, wird die Liste des Aufrufers aktualisiert, und wenn die Liste leer ist, wird sie aktualisiert wird nicht berührt.
Vincent
1
@PatrickT Das richtige Tool hängt vom Fall ab - eine varargs-Funktion unterscheidet sich stark von einer, die ein (optionales) Listenargument verwendet . Situationen, in denen Sie sich zwischen ihnen entscheiden müssten, treten seltener auf als Sie denken. Varargs ist großartig, wenn sich die Anzahl der Argumente ändert, wird jedoch behoben, wenn der Code SCHRIFTLICH ist. Wie dein Beispiel. Wenn es sich um eine Laufzeitvariable handelt oder Sie f()eine Liste aufrufen möchten, müssen Sie f(*l)die Bruttoliste aufrufen . Schlimmer noch, die Implementierung mate(['larch', 'finch', 'robin'], ['bumble', 'honey', 'queen'])würde mit varargs SAUGEN. Viel besser, wenn es so ist def mate(birds=[], bees=[]):.
Ferd
26

Bestehende Antworten haben bereits die gewünschten direkten Lösungen geliefert. Da dies jedoch eine sehr häufige Gefahr für neue Python-Programmierer ist, lohnt es sich, die Erklärung hinzuzufügen, warum sich Python so verhält, die im " Per Anhalter durch Python " als "veränderbare Standardargumente " zusammengefasst ist: http: // docs .python-guide.org / de / latest / writing / gotchas /

Zitat: " Die Standardargumente von Python werden einmal ausgewertet, wenn die Funktion definiert wird, nicht jedes Mal, wenn die Funktion aufgerufen wird (wie beispielsweise Ruby). Dies bedeutet, dass Sie, wenn Sie ein veränderliches Standardargument verwenden und es mutieren, dies tun und haben werden." mutierte dieses Objekt auch für alle zukünftigen Aufrufe der Funktion "

Beispielcode zur Implementierung:

def foo(element, to=None):
    if to is None:
        to = []
    to.append(element)
    return to
Zhenhua
quelle
13

Nicht, dass es in diesem Fall wichtig wäre, aber Sie können die Objektidentität verwenden, um auf Keine zu testen:

if working_list is None: working_list = []

Sie können auch die Vorteile des booleschen Operators oder der Definition in Python nutzen:

working_list = working_list or []

Dies verhält sich jedoch unerwartet, wenn der Anrufer Ihnen eine leere Liste (die als falsch gilt) als Arbeitsliste gibt und erwartet, dass Ihre Funktion die Liste ändert, die er ihr gegeben hat.

Bendin
quelle
10

Wenn die Absicht der Funktion darin besteht, den als übergebenen Parameter zu ändernworking_list , lesen Sie die Antwort von HenryR (= Keine, prüfen Sie, ob darin Keine vorhanden ist).

Wenn Sie das Argument jedoch nicht mutieren wollten, verwenden Sie es einfach als Ausgangspunkt für eine Liste. Sie können es einfach kopieren:

def myFunc(starting_list = []):
    starting_list = list(starting_list)
    starting_list.append("a")
    print starting_list

(oder in diesem einfachen Fall nur, print starting_list + ["a"]aber ich denke, das war nur ein Spielzeugbeispiel)

Im Allgemeinen ist das Mutieren Ihrer Argumente in Python ein schlechter Stil. Die einzigen Funktionen, von denen vollständig erwartet wird, dass sie ein Objekt mutieren, sind Methoden des Objekts. Es ist noch seltener, ein optionales Argument zu mutieren - ist ein Nebeneffekt, der nur bei einigen Aufrufen auftritt, wirklich die beste Schnittstelle?

  • Wenn Sie dies aus der C-Gewohnheit der "Ausgabeargumente" heraus tun, ist dies völlig unnötig - Sie können immer mehrere Werte als Tupel zurückgeben.

  • Wenn Sie dies tun, um eine lange Liste von Ergebnissen effizient zu erstellen, ohne Zwischenlisten zu erstellen, sollten Sie sie als Generator schreiben und verwenden, result_list.extend(myFunc())wenn Sie sie aufrufen. Auf diese Weise bleiben Ihre Anrufkonventionen sehr sauber.

Ein Muster, bei dem häufig ein optionales Argument mutiert wird , ist ein verstecktes "Memo" -Arg in rekursiven Funktionen:

def depth_first_walk_graph(graph, node, _visited=None):
    if _visited is None:
        _visited = set()  # create memo once in top-level call

    if node in _visited:
        return
    _visited.add(node)
    for neighbour in graph[node]:
        depth_first_walk_graph(graph, neighbour, _visited)
Beni Cherniavsky-Paskin
quelle
3

Ich bin vielleicht nicht zum Thema gehörend, aber denken Sie daran, dass die pythonische Methode darin besteht, ein Tupel *argsoder ein Wörterbuch zu übergeben, wenn Sie nur eine variable Anzahl von Argumenten übergeben möchten **kargs. Diese sind optional und besser als die Syntax myFunc([1, 2, 3]).

Wenn Sie ein Tupel übergeben möchten:

def myFunc(arg1, *args):
  print args
  w = []
  w += args
  print w
>>>myFunc(1, 2, 3, 4, 5, 6, 7)
(2, 3, 4, 5, 6, 7)
[2, 3, 4, 5, 6, 7]

Wenn Sie ein Wörterbuch übergeben möchten:

def myFunc(arg1, **kargs):
   print kargs
>>>myFunc(1, option1=2, option2=3)
{'option2' : 2, 'option1' : 3}
Mapad
quelle
0

Es wurden bereits gute und korrekte Antworten gegeben. Ich wollte nur eine andere Syntax angeben, um zu schreiben, was Sie tun möchten, was ich schöner finde, wenn Sie beispielsweise eine Klasse mit standardmäßigen leeren Listen erstellen möchten:

class Node(object):
    def __init__(self, _id, val, parents=None, children=None):
        self.id = _id
        self.val = val
        self.parents = parents if parents is not None else []
        self.children = children if children is not None else []

Dieses Snippet verwendet die if else-Operatorsyntax. Ich mag es besonders, weil es ein hübscher kleiner Einzeiler ohne Doppelpunkte usw. ist und sich fast wie ein normaler englischer Satz liest. :) :)

In deinem Fall könntest du schreiben

def myFunc(working_list=None):
    working_list = [] if working_list is None else working_list
    working_list.append("a")
    print working_list
drssdinblck
quelle
-3

Ich habe die UCSC-Erweiterungsklasse belegt Python for programmer

Was gilt für: def Fn (data = []):

a) ist eine gute Idee, damit Ihre Datenlisten bei jedem Anruf leer beginnen.

b) ist eine gute Idee, damit alle Aufrufe der Funktion, die keine Argumente für den Aufruf liefern, die leere Liste als Daten erhalten.

c) ist eine vernünftige Idee, solange Ihre Daten eine Liste von Zeichenfolgen sind.

d) ist eine schlechte Idee, da der Standardwert [] Daten sammelt und der Standardwert [] sich bei nachfolgenden Aufrufen ändert.

Antworten:

d) ist eine schlechte Idee, da der Standardwert [] Daten sammelt und der Standardwert [] sich bei nachfolgenden Aufrufen ändert.

Peter Chen
quelle