【问题标题】:Why does this code only work half the time?为什么这段代码只工作了一半?
【发布时间】:2011-11-27 03:49:29
【问题描述】:

我有以下测试

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)

我已经有一个测试可以检查 turns_unemployed 变量在调用 employee.turn() 时实际上增加了 1,所以我知道它有效...

真正让我烦恼的是,我的测试只在我运行它的 50% 的时间里有效,我不知道为什么......有人看到任何可能导致任何差异的东西吗?

顺便说一句,运行 Python 3.2.2

【问题讨论】:

  • 拼写:arround -> around
  • 从代码清晰的角度来看,我建议您将__call_employees_turn_function__remove_workers_unemployed_for_3_rounds 内联到next_turn。他们都很矮。只需输入描述性评论,例如# remove workers unemployed for 3 rounds
  • 另外,__remove_workers_unemployed_for_3_rounds非常特定的。如果您稍后更改规则,让失业工人停留四轮怎么办?我认为在这种情况下最好使用一个不太具体(和更短)的名称,如果你打算将它作为一个单独的方法。也许像remove_long_term_unemployed 这样的东西。或者甚至可能是purge_tramps
  • @DannyMilosavljevic:一点也不。代码流越简单,代码就越容易阅读。将两行和三行拆分为单独的函数会使代码更难阅读。 __call_employees_turn_function?这两行本身就是清晰的。 __remove_workers_unemployed_for_3_rounds?如果你觉得特别需要 cmets,就把 cmets 放进去。但是不要在不需要的时候把它拆分成单独的方法。 (我还假设不会从其他任何地方调用这些函数。如果是的话,可能值得添加一个类似的方法。)
  • @RobinHeggelundHansen:正如我所说,“如果你觉得特别需要 cmets。”我会认为发生的事情非常明显,因此可能不会将 cmets 放在那里。

标签: python python-3.x


【解决方案1】:

__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)

例子:

你每回合产生 2 名新员工。在第 4 回合,您有 2 名员工要移除(列表中的前两名)。您开始迭代并删除第一个。该列表现在只有五个项目,但迭代继续并查看第二个项目。问题是第二个项目不再是第二个员工,而是第三个。第二名员工将保留在列表中,您的测试将失败。只有在第一轮只生成一个员工时,您的测试才有效。

【讨论】:

    【解决方案2】:

    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)
    

    但我不知道这是否与您的问题有关,因为我看不到您的其余代码 - 我什至不确定它们是否属于同一类。您可能应该发布您可以获得的最小的完整代码,但它存在问题 - 这样人们就可以实际运行您的代码并自己重现问题。

    【讨论】:

    • 支持您发布足够多的代码以供其他人自己运行的提示。不过,为了解释您的困惑:employees 是一个具有返回 __employees 的 getter 的属性。不过,我很高兴您指出了这一点,因为在我的代码中使用员工而不是 __employees 是一个一致性错误,谢谢! :D
    【解决方案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.
    

    【讨论】:

    • 我不明白为什么这是一个更好的解决方案?基本上,您只是采取了额外的步骤来创建要从中删除的对象的预过滤列表。也不理解您关于对象身份与平等的论点,因为在这两种情况下,您都将迭代对相同对象的引用......不要试图听起来/粗鲁,只是想了解为什么这样做更好?跨度>
    • 我不是“创建要删除的对象的预过滤列表”;过滤会删除不需要的对象。在单个表达式中,您将获得所需的结果,然后只需将其分配回属性即可开始使用它。没有“额外步骤”。你看到的代码就是整个过程。简单胜于复杂。我正在和其他人一起挥手,实际上我现在看不出它会在哪里引起问题,但是迭代引用的副本仍然很丑。
    • 我喜欢这个——避免了原来的问题,用一行代码实现了想要的操作。 +1
    • 啊,当你这样说的时候 ;) 可悲的是,我不能同时接受这两个答案,但我对原始答案和你的更多解释性评论都投了赞成票。 :)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-11-29
    • 1970-01-01
    • 2023-03-05
    • 2015-06-07
    • 2011-12-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多