Python生成器与回调函数

siz*_*erz 6 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()
Run Code Online (Sandbox Code Playgroud)

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

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

   end_time = time.time()
   delta_time = end_time - start_time
Run Code Online (Sandbox Code Playgroud)

在回调版本中,只需一次调用就可以解决循环问题.

Joc*_*zel 3

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

          for tf in self.solve():
             yield tf
Run Code Online (Sandbox Code Playgroud)

这些行递归地循环更深层递归阶段的结果。这意味着在递归的每个级别上都会迭代单个结果,从而导致大量不必要的循环。

让我用这个例子来说明:

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
Run Code Online (Sandbox Code Playgroud)

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

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

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