为什么这段代码只有一半时间有效?

3 投票
3 回答
656 浏览
提问于 2025-04-17 07:08

我有一个测试代码如下:

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)

这个代码的主要功能是生成随机数量的员工,并把这些员工添加到我的 game_object.employees 列表中。当我调用 game_object.next_turn 这个函数时,每个员工都有一个 turns_unemployed 变量,用来记录他们失业的回合数。一旦这个数字达到3,那个员工就会从 game_object.employees 列表中被移除。

接下来是来自 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)

我已经有一个测试,检查当调用 employee.turn() 时,turns_unemployed 变量确实会增加1,所以我知道这一部分是正常的……

让我感到困扰的是,我的测试在运行时只有50%的成功率,我搞不清楚为什么会这样……有没有人能看到可能导致这个问题的原因?

顺便说一下,我使用的是 Python 3.2.2 版本。

3 个回答

3

不要修改你正在遍历的容器。

为了遍历而保留一个副本也是个很糟糕的办法,这样做可能会让你在需要精确区分对象的身份和对象的相等性时遇到麻烦。而且,这样做就是麻烦。

其实有个更简单的方法:采用函数式编程的思路。创建一个新容器,规则是“从原始容器中取出所有不符合删除条件的东西”,然后开始使用这个新容器,而不是原来的那个。

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

Hugo 可能说得对,你的问题是因为在遍历一个列表的时候不能删除里面的项。不过还有另一个可能的问题,就是你创建员工的时候,把他们放在一个叫 __employees 的列表里,也就是:

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

但当你后面遍历这些员工时,你却用的是一个叫 employees 的列表,也就是:

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)

不过我不确定这是否和你的问题有关,因为我看不到你代码的其他部分——我甚至不确定这两个列表是否在同一个类里。你最好发一个最小的完整代码片段,这样大家才能运行你的代码,自己复现这个问题。

4

你在函数 __remove_workers_unemployed_for_3_rounds 中一边遍历列表一边删除里面的项目,这样会导致一些你想删除的项目被跳过。你需要遍历列表的一个副本。

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

举个例子:

每轮你会生成两个新员工。在第4轮时,你有两个员工需要删除(列表中的前两个)。你开始遍历并删除第一个员工。现在列表里只剩下五个项目,但遍历还在继续,这时它查看第二个项目。问题是,第二个项目现在不是原来的第二个员工,而是第三个。这样一来,第二个员工就会留在列表里,你的测试就会失败。如果第一轮只生成一个员工的话,你的测试才会正常工作。

撰写回答