J'ai le test suivant

def test_employees_not_arround_for_more_than_3_rounds(self):
    self.game_object.generate_workers()

    people_in_list_turn_1 = self.game_object.employees[:]
    self.game_object.next_turn()
    self.game_object.generate_workers()
    self.game_object.next_turn()
    self.game_object.generate_workers()
    self.game_object.next_turn()

    for employee in people_in_list_turn_1:
        self.assertFalse(employee in self.game_object.employees)

Fondamentalement, il génère un nombre aléatoire de travailleurs et l'ajoute à ma liste game_object.employees. Lorsque j'appelle la fonction game_object.next_turn, chaque employé a une variable turns_unemployed qui contient le nombre de tours pendant lesquels il a été au chômage, une fois que cela atteint 3, le travailleur sera supprimé de la liste game_object.employees tout à fait.

Voici le code d'implémentation de game_object.py:

def generate_workers(self):
    workersToAdd = range(random.randrange(1,8))
    for i in workersToAdd:
        self.__employees.append(Employee())

def next_turn(self):
    self.__current_turn += 1
    self.__call_employees_turn_function()
    self.__remove_workers_unemployed_for_3_rounds()

def __call_employees_turn_function(self):
    for employee in self.employees:
        employee.turn()

def __remove_workers_unemployed_for_3_rounds(self):
    for employee in self.employees:
        if employee.turns_unemployed >= 3:
            self.employees.remove(employee)

J'ai déjà un test qui vérifie que la variable turns_unemployed est effectivement augmentée d'une unité lorsque employee.turn() est appelée, donc je sais que ça marche ...

La chose qui me dérange vraiment ici, c'est que mon test ne fonctionne que 50% du temps que je l'exécute, et je ne peux pas comprendre pourquoi ... Quelqu'un voit-il quelque chose qui peut causer des écarts?

Btw, exécutant Python 3.2.2

3
Robin Heggelund Hansen 27 nov. 2011 à 07:49

3 réponses

Meilleure réponse

Vous supprimez des éléments d'une liste tout en effectuant une itération sur celle-ci dans __remove_workers_unemployed_for_3_rounds, de sorte que la boucle ignore les éléments que vous souhaitez qu'elle supprime. Vous devez parcourir une copie de la liste.

def __remove_workers_unemployed_for_3_rounds(self):
    for employee in self.employees[:]:
        if employee.turns_unemployed >= 3:
            self.employees.remove(employee)

Exemple:

Vous générez 2 nouveaux employés à chaque tour. Au 4ème tour, vous avez 2 employés à supprimer (les deux premiers dans la liste). Vous commencez l'itération et supprimez le premier. La liste ne contient plus que cinq éléments, mais l'itération se poursuit et examine le deuxième élément. Le problème est que le deuxième élément n'est plus le deuxième employé, mais le troisième. Le deuxième employé restera dans la liste et votre test échouera. Votre test ne fonctionne que si un seul employé est généré au premier tour.

4
Hugo Wood 27 nov. 2011 à 04:31

Ne modifiez pas les conteneurs que vous parcourez.

Garder une copie pour itérer est également un vilain piratage, et cela peut vous faire trébucher plus tard dans les cas où vous devez être vraiment précis sur l'identité d'objet par rapport à l'égalité d'objet. C'est aussi tout simplement désordonné.

Il existe une manière beaucoup plus simple: adopter l'approche de programmation fonctionnelle. Créez un nouveau conteneur à l'aide de la règle "tout ce qui se trouve dans le conteneur d'origine qui ne remplit pas la condition de suppression", puis commencez à utiliser cela au lieu du conteneur d'origine.

def __remove_workers_unemployed_for_3_rounds(self):
    self.employees = filter(lambda e: e.turns_unemployed < 3, self.employees)
    # Or with a list comprehension:
    # self.employees = [e for e in self.employees if e.turns_unemployed < 3]
    # if you find that more readable.
3
Karl Knechtel 27 nov. 2011 à 06:51

Hugo a probablement raison sur la cause de votre problème; vous ne pouvez pas supprimer des éléments d'une liste pendant que vous l'itérez. Voici un autre problème possible, lorsque vous créez des employés, vous les mettez dans une liste appelée __employees, c'est-à-dire

def generate_workers(self):
    workersToAdd = range(random.randrange(1,8))
    for i in workersToAdd:
        self.__employees.append(Employee())

Mais lorsque vous les parcourez plus tard, vous utilisez une liste appelée employés, c'est-à-dire

def __call_employees_turn_function(self):
    for employee in self.employees:
        employee.turn()

def __remove_workers_unemployed_for_3_rounds(self):
    for employee in self.employees:
        if employee.turns_unemployed >= 3:
            self.employees.remove(employee)

Mais je ne sais pas si cela est lié à votre problème parce que je ne peux pas voir le reste de votre code - je ne suis même pas sûr que ce soit dans la même classe ou non. Vous devriez probablement publier le plus petit morceau de code complet que vous pouvez obtenir qui a le problème - de cette façon, les gens peuvent réellement exécuter votre code et reproduire le problème pour eux-mêmes.

3
Philip Uren 27 nov. 2011 à 04:40