Python生成器与回调函数

时间:2011-04-18 14:17:36

标签: python profiling generator

我有一个类使用递归的回溯算法解决了一个确切的覆盖问题。最初,我使用在初始化期间传递给对象的回调函数实现了类。无论何时找到解决方案,都会调用此回调。在查看其他人对同一问题的实现时,我看到他们使用yield语句来传递解决方案,换句话说,他们的代码是一个python生成器。我认为这是一个有趣的想法,所以我创建了我的课程的新版本以使用收益率。然后,我在两个版本之间运行了比较测试,令我惊讶的是,我发现生成器版本运行速度比回调版本慢5倍。请注意,除了切换回调的yield之外,代码是相同的。

这里发生了什么?我猜测,因为生成器需要在屈服之前保存状态信息,然后在下次调用时重新启动时恢复该状态,正是这种保存/恢复使生成器版本运行得慢得多。如果是这种情况,生成器必须保存和恢复多少状态信息?

来自python专家的任何想法?

- 太平洋时间7:40编辑

这是使用yield的求解器代码。通过调用回调函数替换下面的第一个yield,并将第二个yield更改为以下循环,只需一个递归调用来解析此代码的原始版本。

   def solve(self):
      for tp in self.pieces:
         if self.inuse[tp.name]: continue

         self.inuse[tp.name] = True
         while tp.next_orientation() is not None:
            if tp.insert_piece():
               self.n_trials += 1
               self.pieces_in += 1
               self.free_cells -= tp.size

               if self.pieces_in == len(self.pieces) or self.free_cells == 0:
                  self.solutions += 1
                  self.haveSolution = True
                  yield True
                  self.haveSolution = False
               else:
                  self.table.next_base_square()
                  for tf in self.solve():
                     yield tf

               tp.remove_piece()
               self.pieces_in -= 1
               self.table.set_base_square(tp.base_square)
               self.free_cells += tp.size

         self.inuse[tp.name] = False
         tp.reset_orientation()

调用求解器的邮件循环(当然是在初始化之后)是

   start_time = time.time()
   for tf in s.solve():
      printit(s)

   end_time = time.time()
   delta_time = end_time - start_time

在回调版本中,只需一次调用即可完成循环。

1 个答案:

答案 0 :(得分:2)

我在评论中的意思是,(“从递归函数中产生的声音听起来需要额外的循环才能将结果传递给调用者”)是这一行:

          for tf in self.solve():
             yield tf

这些行以递归方式循环来自更深递归阶段的结果。这意味着在递归的每个级别上迭代单个结果,导致很多不必要的循环。

让我用这个例子来说明:

n = 0
def rekurse(z):
    global n
    if z:
        yield z
        for x in rekurse(z-1):
            n += 1
            yield x

print list(rekurse(10))
print n

正如您所看到的,这只是从10开始倒计时,因此您可以期望线性迭代次数。你可以看到n以二次方式增长 - recurse(10)循环超过9项,recurse(9)超过8项,依此类推。

你拥有的项目越多,Python花在这些简单线条上的时间就越多。回调完全避免了这个问题,所以我怀疑你的代码存在问题。

PEP 380的优化实施可以解决此问题(请参阅this paragraph)。与此同时,我不认为从递归函数中产生(至少如果它们深度递归),它们只是不能很好地协同工作。