2009-10-31 13 views
6

Naprawdę utknąłem na tym, dlaczego poniższy blok kodu 1 powoduje wyjście 1 zamiast wyjścia 2?Python Scoping/Static Misunderstanding

blok kodowy 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,] 

Celem tego kodu jest iterację tablicy i zawijać w każdy obiekt macierzystego. Jest to zmniejszenie mojego rzeczywistego kodu, który dodaje wszystkie jabłka do torby z jabłkami i tak dalej. Domyślam się, że z jakiegoś powodu używa tego samego obiektu lub działa tak, jakby pojemnik z owocami korzystał z tablicy statycznej. Nie mam pojęcia, jak to naprawić.

+1

Nie jest to odpowiedź na twoje pytanie, ale także godne uwagi: "while len (arrayOfFruit)> 0:" jest równoważne "while while arrayOfFruit:".To ostatnie jest lepsze, przynajmniej według Python Style Guide. –

Odpowiedz

2

Twój kod ma domyślny argument zainicjować klasę. Wartość domyślnego argumentu jest oceniana raz, podczas kompilacji, więc każde wystąpienie jest inicjowane z tą samą listą. Zmień go tak:

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

omówiłem ten pełniej tutaj: How to define a class in Python

8

Nigdy nie należy używać wartości zmiennej (jak []) do domyślnego argumentu metody. Wartość jest obliczana raz, a następnie używana dla każdego wywołania. Gdy używasz pustej listy jako wartości domyślnej, ta sama lista jest używana za każdym razem, gdy metoda jest wywoływana bez argumentu, nawet jeśli wartość jest modyfikowana przez poprzednie wywołania funkcji.

Czy to zamiast:

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

Idealny !!! To jest fantastyczne i proste. –

+4

Naprawdę nie lubię, gdy testujesz "Brak", sprawdzając, czy wartość jest fałszywa. Lepiej jest użyć testu 'is None". Osoba dzwoniąca może legalnie przekazać pustą listę inicjatorowi, a twój kod odrzuci tę pustą listę i stworzy świeżą pustą listę ... to pojawiłoby się tylko wtedy, gdyby ktoś próbował zrobić kilka wystąpień tej samej klasy. początkowo pusta lista, jak sądzę, ale jest to możliwe. W każdym razie używanie "jest" do testowania na "Brak" jest dobrym nawykiem. – steveha

+0

Masz rację, "jest Brak" jest bezpieczniejsze. –

1

Jak mówi Ned, problem jest używasz listę jako domyślny argument. Jest więcej szczegółów here. Rozwiązaniem jest zmiana __init__ funkcję jak poniżej:

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

Lepszym rozwiązaniem niż przechodzącą w None - w tym konkretnym przypadku, a nie w ogóle - jest potraktowanie parametr arr do __init__ jako przeliczalny zbiór elementów wstępnie zainicjować FruitContainer z, zamiast tablic użyć do pamięci wewnętrznej:

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

To pozwoli Ci zdać w innych typach przeliczalnych zainicjować swój pojemnik, który bardziej zaawansowanych użytkowników Python będzie oczekiwać, aby być w stanie to zrobić:

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

Będzie również rozbroić kolejną potencjalną aliasingu błąd:

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

ze wszystkimi innymi implementacjami na tej stronie, będzie to powrót prawda, ponieważ przypadkowo spopularyzowałeś wewnętrzne przechowywanie swoich kontenerów.

Uwaga: Takie podejście nie zawsze jest prawidłowa odpowiedź: „jeśli nie brak” jest lepszy w innych przypadkach. Po prostu zadaj sobie pytanie: czy przechodzę w zestawie przedmiotów lub zmiennym pojemniku? Jeśli klasa/funkcja, którą przekazuję swoim obiektom, aby zmienić pamięć, którą dałem, czy byłaby (a) zaskakująca lub (b) pożądana? W tym przypadku argumentowałbym, że jest (a); w związku z tym wywołanie list (...) jest najlepszym rozwiązaniem. Jeśli (b), właściwym rozwiązaniem byłoby "jeśli nie żadne".

+1

Cześć, chyba jestem "kimś". Nie mam zastrzeżeń do kodowania tego przykładu w ten sposób. W tym przykładzie podajesz owoce, aby dodać je do pamięci wewnętrznej w klasie FruitContainer. Użytkownik nie tyle przechodzi w obiekt listy, ile inicjuje kontener z pewnymi owocami. Teraz, w ogólnym przypadku, nie wydaje mi się, że dobrze jest w ciszy wymuszać rzeczy, które zapewnia użytkownik.Myślę, że powodem, dla którego rozmawialiśmy o sobie nawzajem jest to, że skupiłem się na ogólnej sprawie i myślałeś o tym bardzo konkretnym przypadku. * Ogólnie rzecz biorąc, * nie przerywaj pisania na klawiaturze przez wymuszanie typów. – steveha

+1

Doskonały. Przepisałem ostatni akapit, aby to odzwierciedlić. Dziękuję za poświęcenie czasu, aby Twój punkt był dla mnie bardziej przejrzysty! –

+0

Przepraszam, że nie wiedziałem, skąd przybywasz wcześniej. Byłem w pełnym trybie pedantycznym, jak sądzę. :-) – steveha