2009-10-31 7 views
6

Ich bin wirklich fest, warum der folgende Codeblock 1 Ausgabe 1 anstelle von Ausgabe 2 führen?Python Scoping/Static Misunderstanding

-Code-Block 1:

class FruitContainer: 
     def __init__(self,arr=[]): 
      self.array = arr 
     def addTo(self,something): 
      self.array.append(something) 
     def __str__(self): 
      ret = "[" 
      for item in self.array: 
       ret = "%s%s," % (ret,item) 
      return "%s]" % ret 

arrayOfFruit = ['apple', 'banana', 'pear'] 
arrayOfFruitContainers = [] 

while len(arrayOfFruit) > 0: 
    tempFruit = arrayOfFruit.pop(0) 
    tempB = FruitContainer() 
    tempB.addTo(tempFruit) 
    arrayOfFruitContainers.append(tempB) 

for container in arrayOfFruitContainers: 
    print container 

**Output 1 (actual):** 
[apple,banana,pear,] 
[apple,banana,pear,] 
[apple,banana,pear,] 

**Output 2 (desired):** 
[apple,] 
[banana,] 
[pear,] 

Ziel dieses Code ist durch eine Anordnung und wickelt die jeweils in einem übergeordneten Objekt iterieren. Dies ist eine Reduzierung meines tatsächlichen Codes, der alle Äpfel zu einer Tüte Äpfel und so weiter hinzufügt. Meine Vermutung ist, dass es aus irgendeinem Grund entweder das gleiche Objekt verwendet oder so handelt, als ob der Fruchtcontainer ein statisches Array verwendet. Ich habe keine Ahnung, wie ich das beheben kann.

+1

Keine Antwort auf Ihre Frage, aber auch bemerkenswert: "while len (arrayOfFruit)> 0:" ist äquivalent zu "while arrayOfFruit:".Letzteres ist zumindest dem Python Style Guide vorzuziehen. –

Antwort

2

Ihr Code hat ein Standardargument, die Klasse zu initialisieren. Der Wert des Standardarguments wird einmal zur Kompilierzeit ausgewertet, sodass jede Instanz mit derselben Liste initialisiert wird. Ändern Sie es wie folgt:

def __init__(self, arr=None): 
    if arr is None: 
     self.array = [] 
    else: 
     self.array = arr 

Ich sprach darüber ausführlicher hier: How to define a class in Python

8

Sie sollten niemals einen veränderbaren Wert (wie []) für ein Standardargument einer Methode verwenden. Der Wert wird einmal berechnet und dann für jeden Aufruf verwendet. Wenn Sie eine leere Liste als Standardwert verwenden, wird dieselbe Liste jedes Mal verwendet, wenn die Methode ohne das Argument aufgerufen wird, auch wenn der Wert durch vorherige Funktionsaufrufe geändert wird.

Tun Sie dies statt:

def __init__(self,arr=None): 
    self.array = arr or [] 
+0

Perfekt !!! Das ist fantastisch und einfach. –

+4

Ich mag es wirklich nicht zu sehen, dass Sie für 'None' testen, indem Sie sehen, ob der Wert falsch ist. Es ist besser, einen "ist keine" -Test zu verwenden. Ein Anrufer könnte legal eine leere Liste für den Initialisierer übergeben, und Ihr Code würde diese leere Liste verwerfen und eine neue leere Liste erstellen ... dies würde nur auftreten, wenn jemand versucht, mehrere Instanzen der Klasse alle gleich zu machen anfangs leere Liste, ich denke, aber es ist möglich. In jedem Fall ist die Verwendung von "ist", um auf "keine" zu testen, eine gute Angewohnheit. – steveha

+0

Sie haben Recht, 'ist keiner' ist sicherer. –

1

Als Ned sagt, das Problem ist, dass Sie eine Liste als Standardargument verwenden. Es gibt mehr Details here. Die Lösung ist __init__ Funktion zu verändern, wie unten:

 def __init__(self,arr=None): 
      if arr is not None: 
       self.array = arr 
      else: 
       self.array = [] 
0

Eine bessere Lösung als in None vorbei - in diesem speziellen Fall, anstatt im Allgemeinen - ist die arr Parameter zu behandeln __init__ als eine zählbare Menge von Elementen vorab zu initialisieren FruitContainer mit, anstatt ein Array für interne Speicher zu verwenden:

class FruitContainer: 
    def __init__(self, arr=()): 
    self.array = list(arr) 
    ... 

Diese Sie in anderen zählbaren Arten passieren können Ihren Behälter initialisieren, die erweiterte Python-Nutzer erwarten zu können, tun:

myFruit = ('apple', 'pear') # Pass a tuple 
myFruitContainer = FruitContainer(myFruit) 
myOtherFruit = file('fruitFile', 'r') # Pass a file 
myOtherFruitContainer = FruitContainer(myOtherFruit) 

Es wird auch ein anderes Potenzial Aliasing Fehler entschärfen:

myFruit = ['apple', 'pear'] 
myFruitContainer1 = FruitContainer(myFruit) 
myFruitContainer2 = FruitContainer(myFruit) 
myFruitContainer1.addTo('banana') 
'banana' in str(myFruitContainer2) 

Bei allen anderen Implementierungen auf dieser Seite, wird diese wahre zurückkehren, weil Sie haben versehentlich den internen Speicher Ihrer Container mit Aliasnamen versehen.

Hinweis: Dieser Ansatz ist nicht immer die richtige Antwort: „Wenn nicht None“ in anderen Fällen besser ist. Fragen Sie sich einfach: übergebe ich eine Reihe von Objekten oder einen veränderlichen Container? Wenn die Klasse/Funktion, an der ich meine Objekte übergebe, den Speicher ändert, den ich ihr gegeben habe, wäre das (a) überraschend oder (b) wünschenswert? In diesem Fall würde ich argumentieren, dass es (a) ist; Daher ist der Aufruf der Liste (...) die beste Lösung. Wenn (b), wäre "wenn nicht" der richtige Ansatz.

+1

Hallo, ich denke ich bin "jemand". Ich habe nichts dagegen, dieses Beispiel so zu kodieren. In diesem Beispiel übergeben Sie Früchte, die dem internen Speicher in der FruitContainer-Klasse hinzugefügt werden sollen. Der Benutzer gibt nicht so sehr ein Listenobjekt weiter, als den Container mit etwas Frucht zu initialisieren. Nun, im allgemeinen Fall halte ich es nicht für eine gute Idee, die Dinge, die der Benutzer anbietet, stillschweigend zu erzwingen.Ich denke, der Grund, warum wir aneinander vorbeigegangen sind, ist, dass ich mich auf den allgemeinen Fall konzentriert habe und Sie an diesen sehr spezifischen Fall gedacht haben. * Im Allgemeinen, * nicht brechen Ente Eingabe durch Nötigung Arten. – steveha

+1

Ausgezeichnet. Ich habe meinen letzten Absatz umgeschrieben, um das zu reflektieren. Danke, dass du dir die Zeit genommen hast, mir deinen Standpunkt klarer zu machen! –

+0

Es tut mir nur leid, dass ich nicht herausgefunden habe, woher du kommst. Ich war im pedanten Modus. :-) – steveha