Frage

hatte ich eine sehr schwierige Zeit mit dem Verständnis der Ursache eines Problems in einem Algorithmus. Dann wird durch die Funktionen Schritt für Schritt der Vereinfachung fand ich heraus, dass die Bewertung der Standardargumente in Python nicht so tun, als ich erwartet habe.

Der Code lautet wie folgt:

class Node(object):
    def __init__(self, children = []):
        self.children = children

Das Problem ist, dass jede Instanz der Klasse Knoten teilt das gleiche children Attribut, wenn das Attribut gegeben ist nicht explizit, wie zum Beispiel:

>>> n0 = Node()
>>> n1 = Node()
>>> id(n1.children)
Out[0]: 25000176
>>> id(n0.children)
Out[0]: 25000176

Ich verstehe nicht, die Logik dieser Design-Entscheidung? Warum haben Python Designer entscheiden, dass Standardargumente sind zur Definitionszeit ausgewertet werden? Dies scheint sehr kontraintuitiv zu mir.

War es hilfreich?

Lösung

Die Alternative wäre ziemlich Schwergewicht sein - „Standardargument Werte“ in dem Funktionsobjekt als „Thunk“ des Codes zu speichern und immer wieder die Funktion ohne einen bestimmten Wert für dieses Argument aufgerufen wird jedes Mal ausgeführt werden - und wäre es viel schwieriger, die frühe Bindung zu erhalten (bei def Zeit Bindung), die oft ist das, was Sie wollen. Zum Beispiel in Python, wie es vorhanden ist:

def ack(m, n, _memo={}):
  key = m, n
  if key not in _memo:
    if m==0: v = n + 1
    elif n==0: v = ack(m-1, 1)
    else: v = ack(m-1, ack(m, n-1))
    _memo[key] = v
  return _memo[key]

... Schreiben eine memoized Funktion wie die oben ist eine ganz elementare Aufgabe. Ähnlich:

for i in range(len(buttons)):
  buttons[i].onclick(lambda i=i: say('button %s', i))

... die einfache i=i am frühen Bindung (Definitionszeit) unter Berufung Ausfall arg Werte, ist eine trivialer einfache Art und Weise der frühen Bindung zu erhalten. So ist die aktuelle Regel einfach, unkompliziert und lässt Sie tun alles, was Sie in einer Art und Weise wollen, die extrem einfach zu erklären und zu verstehen: wenn Sie die späte Bindung eines Ausdrucks den Wert wollen, dass die Expression in der Funktionskörper bewerten; wenn Sie die frühe Bindung wollen, bewerten es als Standardwert eines arg.

Die Alternative, zwingt spät für beide Situation zu binden, würde diese Flexibilität nicht bieten, und Sie würden durch Reifen gehen zwingen (wie Ihre Funktion in eine Schließung Fabrik Verpackung) jedes Mal, wenn die frühe Bindung erforderlich ist, wie in den obigen Beispielen -. noch mehr Schwergewicht vorformulierten auf dem Programmierer von dieser hypothetischen Design-Entscheidung (über die „unsichtbaren“ Einsen zu erzeugen und wiederholt Thunks ganzen Ort Auswertung) gezwungen

Mit anderen Worten: „Es sollte eine sein, und vorzugsweise nur eine offensichtliche Art und Weise, es zu tun [1]“: wenn Sie die späten Bindung wollen, gibt es schon eine ganz offensichtliche Art und Weise, es zu erreichen (da alle die Code der Funktion nur bei Anrufzeit ausgeführt wird, offensichtlich alles ausgewertet es ist spät gebunden); default-Arg- Auswertung mit produzieren frühe Bindung gibt Ihnen eine offensichtliche Art und Weise die frühe Bindung als auch zu erreichen (ein Plus! -) statt zwei offensichtliche Wege geben späte Bindung und keine offensichtliche Möglichkeit zu bekommen frühe Bindung zu bekommen (ein Minus -!).

[1]: "Obwohl auf diese Weise zunächst nicht offensichtlich sein, es sei denn, Sie Dutch sind"

Andere Tipps

Das Problem dieser ist.

Es ist zu teuer eine Funktion als initializer jedes Mal die Funktion aufgerufen zu bewerten ist, .

  • 0 ist eine einfache wörtliche. Bewerten Sie es einmal, verwenden Sie es für immer.

  • int ist eine Funktion (wie Liste), das jedes Mal ausgewertet werden müßte es als initializer erforderlich ist.

Das Konstrukt [] ist wörtlich, wie 0, das bedeutet "genau dieses Objekt".

Das Problem ist, dass einige Leute hoffen, dass es bedeutet, list wie in „für mich, diese Funktion zu bewerten, bitte, das Objekt zu erhalten, dass die initializer sind“.

Es wäre eine erdrückende Last sein, die notwendige if Anweisung hinzufügen dieser Bewertung die ganze Zeit zu tun. Es ist besser, alle Argumente als Literale zu nehmen und keine zusätzliche Funktionsauswertung im Rahmen der versucht, eine Funktionsauswertung zu tun.

Auch grundsätzlicher, es ist technisch unmöglich Argument Standardwerte als Funktionsauswertungen zu implementieren.

Nehmen wir für einen Moment die rekursive Schrecken dieser Art von Zirkularität. Lassen Sie uns sagen, dass anstelle der Standardwerte Literale sein, erlauben wir ihnen Funktionen zu sein, die jedes Mal einen Parameterstandard ausgewertet werden Werte erforderlich sind.

[Dies würde die Art und Weise collections.defaultdict Arbeiten parallel.]

def aFunc( a=another_func ):
    return a*2

def another_func( b=aFunc ):
    return b*3

Was ist der Wert von another_func()? Um den Standard für b zu erhalten, müssen sie bewerten aFunc, die eine eval von another_func erfordert. Oops.

Natürlich in Ihrer Situation ist es schwer zu verstehen. Aber Sie müssen sehen, dass jedes Mal, Standard-args Bewertung Last legen würde eine schwere Laufzeit auf dem System.

Sie sollten auch wissen, dass dieses Problem bei Containertypen auftreten kann - aber man könnte es umgehen, indem sie das, was explizit zu machen:

def __init__(self, children = None):
    if children is None:
       children = []
    self.children = children

Die Abhilfe für dieses hier diskutiert (und sehr Feststoff), ist:

class Node(object):
    def __init__(self, children = None):
        self.children = [] if children is None else children

Was, warum von von Löwis nach einer Antwort suchen, aber es ist wahrscheinlich, weil die Funktionsdefinition ein Codeobjekt aufgrund der Architektur von Python macht, und es könnte nicht eine Einrichtung für das Arbeiten mit Referenztypen wie dies in Standardargumenten sein.

Ich dachte, das eingängig war auch, bis ich gelernt, wie Python Standardargumente implementiert.

Eine Funktion ist ein Objekt. Bei der Ladezeit, erstellt Python der Funktion Objekt wertet die Standardwerte in der def Anweisung, sie in ein Tupel setzen, und fügt hinzu, dass Tupel als Attribut der Funktion mit dem Namen func_defaults. Dann, wenn eine Funktion aufgerufen wird, wenn der Anruf nicht einen Wert liefert, Python greift den Standardwert aus func_defaults.

Zum Beispiel:

>>> class C():
        pass

>>> def f(x=C()):
        pass

>>> f.func_defaults
(<__main__.C instance at 0x0298D4B8>,)

So werden alle Anrufe zu f, die kein Argument liefern die gleiche Instanz von C verwenden, denn das ist der Standardwert.

Was, warum Python tut es auf diese Weise: na ja, das Tupel könnte enthält Funktionen, die jedes Mal, wenn ein Standard-Argument-Wert war nötig aufgerufen würde. Neben dem unmittelbar offensichtlichen Problem der Leistung, starten Sie in ein Universum von Spezialfällen bekommen, wie Literalwerte Speichern anstelle von Funktionen für nicht-veränderbare Typen, um unnötige Funktionsaufrufe zu vermeiden. Und natürlich gibt es Auswirkungen auf die Leistung in Hülle und Fülle.

Das tatsächliche Verhalten ist wirklich einfach. Und es gibt ein triviales Problem zu umgehen, in dem Fall, dass Sie will ein Standardwert durch einen Funktionsaufruf zur Laufzeit erzeugt werden:

def f(x = None):
   if x == None:
      x = g()

Das kommt von Python Betonung der Syntax und Ausführung Einfachheit. eine def-Anweisung auftritt, an einem bestimmten Punkt während der Ausführung. Wenn der Python-Interpreter diesen Punkt erreicht, wertet sie den Code in dieser Zeile, und erstellt dann ein Codeobjekt aus dem Körper der Funktion, die später ausgeführt werden soll, wenn Sie die Funktion aufrufen.

Es ist eine einfache Trennung zwischen Funktionsdeklaration und Funktionskörper. Die Erklärung wird ausgeführt, wenn es im Code erreicht ist. Der Körper wird zum Aufrufzeitpunkt ausgeführt. Beachten Sie, dass die Erklärung jedes Mal ausgeführt wird, es erreicht ist, so können Sie mehrere Funktionen erstellen, indem Looping.

funcs = []
for x in xrange(5):
    def foo(x=x, lst=[]):
        lst.append(x)
        return lst
    funcs.append(foo)
for func in funcs:
    print "1: ", func()
    print "2: ", func()

Fünf separate Funktionen erstellt wurden, mit einer separaten Liste jedes Mal, wenn die Funktionsdeklaration ausgeführt wurde erstellt. Auf jeder Schleife durch funcs wird die gleiche Funktion zweimal bei jedem Durchlauf durch, unter Verwendung der gleichen Liste jedes Mal ausgeführt. Dies sind die Ergebnisse:

1:  [0]
2:  [0, 0]
1:  [1]
2:  [1, 1]
1:  [2]
2:  [2, 2]
1:  [3]
2:  [3, 3]
1:  [4]
2:  [4, 4]

Andere haben Sie die Abhilfe gegeben, der mit param = None, und Zuordnen einer Liste im Körper, wenn der Wert None ist, die vollständig idiomatische Python ist. Es ist ein wenig hässlich, aber die Einfachheit ist mächtig, und die Abhilfe ist nicht zu schmerzhaft.

Edited hinzufügen: Weitere Diskussion zu diesem Thema finden effbot den Artikel hier: http: // effbot.org/zone/default-values.htm und die Sprachreferenz, hier: http://docs.python.org/reference/compound_stmts.html#function

Python Funktionsdefinitionen sind nur Code, wie alle anderen Codes; sie sind nicht „magische“ in der Art und Weise, dass einige Sprachen sind. Zum Beispiel in Java könnten Sie „jetzt“ zu etwas definiert „später“ beziehen:

public static void foo() { bar(); }
public static void main(String[] args) { foo(); }
public static void bar() {}

aber in Python

def foo(): bar()
foo()   # boom! "bar" has no binding yet
def bar(): pass
foo()   # ok

So wird das Standardargument zur Zeit ausgewertet, dass die Codezeile ausgewertet wird!

Weil, wenn sie haben, dann würde jemand eine Frage stellen zu fragen, warum es um :-P nicht anders war

Nehmen wir nun an, dass sie hatten. Wie würden Sie das aktuelle Verhalten implementieren, wenn nötig? Es ist einfach, neue Objekte in einer Funktion zu erstellen, aber Sie können nicht „unerschaffen“ sie (Sie können sie löschen, aber es ist nicht das gleiche).

Ich werde eine abweichende Meinung schaffen, durch die Hauptargumente in den anderen Beiträgen addessing.

  

Auswertung Standardargumente, wenn die Funktion ausgeführt wird für die Leistung schlecht sein würde.

Das finde ich schwer zu glauben. Wenn Standardargument Zuweisungen wie foo='some_string' wirklich eine inakzeptable Menge an Aufwand hinzufügen, ich bin sicher, dass es möglich wäre, Zuweisungen unveränderlich Literale zu identifizieren und precompute sie.

  

Wenn Sie eine Vorbelegung mit einem veränderlichen Objekt wie foo = [], benutzen Sie einfach foo = None, gefolgt von foo = foo or [] im Funktionskörper.

Während dies im Einzelfall unproblematisch sein kann, als Design-Muster ist es nicht sehr elegant. Es fügt Standardcode und verschleiert Standardargument Werte. Muster wie foo = foo or ... funktionieren nicht, wenn foo ein Objekt wie ein numpy Array mit nicht definierten Wahrheitswert sein kann. Und in Situationen, in denen None ein sinnvolles Argument Wert ist, der absichtlich übergeben werden kann, kann es nicht als Sentinel verwendet werden und diese Problemumgehung wird wirklich hässlich.

  

Das aktuelle Verhalten ist für veränderbare Standardobjekte nützlich, dass sollte accross Funktionsaufrufe geteilt werden.

Ich würde gerne Beweise für das Gegenteil zu sehen, aber in meiner Erfahrung dieser Anwendungsfall ist viel weniger häufig als veränderbare Objekte, die erstellt werden soll, jedes Mal aufs Neue die Funktion aufgerufen wird. Mir scheint es auch wie eine erweiterte Anwendungsfall, während zufällige Standardzuweisungen mit leeren Behältern eine gemeinsame Gotcha für neue Python-Programmierer sind. Daher sollte das Prinzip der geringsten Erstaunen schlägt Standardargument Werte ausgewertet werden, wenn die Funktion ausgeführt wird.

Darüber hinaus scheint es mir, dass es eine einfache Abhilfe für veränderbare Objekte vorhanden, die über Funktion freigegeben werden sollen, ruft:. Initialisieren sie außerhalb der Funktion

So würde ich argumentieren, dass dies eine schlechte Design-Entscheidung war. Meine Vermutung ist, dass es wurde gewählt, weil ihre Umsetzung tatsächlich einfacher ist und weil es ein gültiges hat Fall verwenden (wenn auch begrenzt). Leider habe ich glaube nicht, das jemals ändern wird, da der Kern Python-Entwickler eine Wiederholung der Menge rückwärts Inkompatibilität vermeiden möge, den Python 3 eingeführt.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top