关于 Python 'yield' 关键字的问题,我在其他地方找不到答案,以及它在我正在处理的代码中的具体用途

Questions about Python 'yield' keyword that I have not found answers elsewhere, and its specific use in a code I am working on

我遇到了一个 python 交给我的有效脚本。我理解该脚本的目的及其在与其他模块交互的大局中的作用,以及它在大多数地方的内部架构。但是,我必须对该脚本进行大修,主要是删除一些旧的 classes 并添加大量新的 subclasses,以便它提供我们需要的更多功能。我的问题主要来自于我所看到的某些函数返回包含该对象的列表与 yield 将该对象返回自身之间的一些无法解释的差异。

# These functions are methods that belong to a class. 
# There is a top level script that instantiates that class and calls
# these methods on that class, and depending on the `self.mode` variable located in the instance namespace, it invokes the different subsequent methods, 
# which are either generateHybridSim() or generatePureHwSim()
# It is worth pointing out here that HybridSimStep and ShangHWSimStep
# are both classes themselves and that they will be instantiated later on as
# I will describe after this chunk of code

def generateSubTests(self) :
  if self.mode == TestStep.HybridSim :
    return self.generateHybridSim()
  elif self.mode == TestStep.PureHWSim or self.mode == TestStep.AlteraSyn \
     or self.mode == TestStep.AlteraNls or self.mode == TestStep.XilinxSyn  :
  return self.generatePureHWSim()

return []

def generateHybridSim(self) :
  return [ HybridSimStep(self) ]

def generatePureHWSim(self) :
  yield ShangHWSimStep(self)
  num_iter = self.max_scheduling_iterations
  if num_iter > 1 :
    for i in range(1) :
      sim_step = ShangHWSimStep(self)
      sim_step.option = self.option.copy()
      sim_step.hls_base_dir = os.path.join(sim_step.hls_base_dir, str(i))
      sim_step.rtl_output = os.path.join(sim_step.hls_base_dir, sim_step.test_name + ".sv")
      sim_step.option['max_scheduling_iterations'] = i + 1
      yield sim_step

最终,无论 generateHybridSim() 还是 generatePureHwSim() 方法被调用,它们都会以完全相同的方式在另一个模块中被调用:

# The 'job' that is in front of generateSubTests() is the instance's
# variable name, and you can see that prepareTest() and runTest()
# methods get called on the subtest iterable object, which so happens
# to be class's instance.
# So in short, these two methods are not defined within generateSubTests() method, but
# rather under the classes that the generateHybridSim() and 
# generatePureHWSim() methods had returned or yielded respectively.

for subtest in job.generateSubTests() :
      subtest.prepareTest()
      subtest.runTest()
      time.sleep(1)
      next_active_jobs.append(subtest)

我现在真的很困惑,我不知道这里使用yieldreturn有什么意义,我需要弄清楚为什么以前的程序员写这个脚本就是这样做的。这是因为我将实现新的 subclasses,它们本身必须包含自己的 generateSubTests() 方法并且必须遵循相同的函数调用。他做了 for subtest in job.generateSubTests 的事实意味着我只能返回一个包含 class 的列表,或者产生 class 本身,否则它不适合 python for循环迭代协议。我已经尝试通过将 generatePureHWSim() 中的 yield 语句修改为 return 中的语句来测试代码,就像 generateHybridSim() 中的那样,它似乎 运行 很好,虽然我可以'不确定是否引入了任何细微的错误。但是,我不知道我是否在这里遗漏了什么。之前的程序员是否想通过使用 yield 将函数转换为生成器来促进并发性 http://www.dabeaz.com/coroutines/index.html

他已经完全离开了我们的实验室,所以我无法向他寻求帮助。

另外,我已经从包括 post 在内的各种来源阅读了关于 yield 的文章: What does the "yield" keyword do in Python?;尽管他们帮助我理解了 yield 的作用,但我仍然不明白在这里使用它对我们的上下文有何帮助。事实上,我什至不明白为什么以前的程序员要用 for subtest in job.generateSubTests() : 实现一个循环并强制 generatePureHWSim()generateHybridSim() 方法本身必须是生成器,这样我们就可以有一个循环只是为了在实例上调用 prepareTest()runTest() 的其他方法。为什么他不能直接返回 class 并调用那些方法???

这真是把我绊倒了。我将非常感谢这里的任何帮助!!!谢谢。

PS:还有一个问题——我注意到一般来说,如果你有一个你定义为的函数:

def a():
  return b
  print "Now we return c"
  return c

似乎每当执行其中的第一条语句并返回 b 时,函数就会完成执行并且永远不会返回 c,因为 return b 之后的语句永远不会被触及。尝试添加打印语句,您会发现它永远不会被打印出来。

然而,当谈到yield时:

def x():
  yield y
  print "Now we yield z"
  yield z

我注意到即使在第一个 yield y 语句执行后,后续的 yield z 也会被执行。尝试添加 print 语句,您会看到它被打印出来了。这是我在调试上述代码时观察到的,我不理解 yieldreturn 之间的这种行为差异。有人可以请教我吗?

谢谢。

我很高兴地告诉你,这里不涉及并发。

之前的程序员想要 generateSubTests return 一组子测试(可能是 0、1 或更多子测试)。然后将在 for subtest in job.generateSubTests(): 循环中相应地处理每个子测试。

实际上,如果您仔细观察,generateHybridSim return 是一个普通的 Python 列表,其中包含一个子测试,而不是生成器对象。但是列表和生成器对象在这个上下文中实际上是非常相似的东西——一系列子测试。

你必须意识到 generatePureHWSim(self) 几乎等同于以下代码:

def generatePureHWSim(self) :
  output_list = []
  output_list.append(ShangHWSimStep(self))
  num_iter = self.max_scheduling_iterations
  if num_iter > 1 :
    for i in range(1) :
      sim_step = ShangHWSimStep(self)
      sim_step.option = self.option.copy()
      sim_step.hls_base_dir = os.path.join(sim_step.hls_base_dir, str(i))
      sim_step.rtl_output = os.path.join(sim_step.hls_base_dir, sim_step.test_name + ".sv")
      sim_step.option['max_scheduling_iterations'] = i + 1
      output_list.append(sim_step)
  return output_list

但有一个例外。虽然上面的代码预先进行了所有计算并将所有结果放入内存中的列表中,但带有 yield 的版本将立即生成单个子测试,并且仅在要求下一个结果时才进行以下计算。

这有很多潜在的好处,包括:

  1. 节省内存(数据一次只加载一个,而不是一次加载到列表中)
  2. 节省计算(如果您可以根据 returned 的内容提前跳出循环)
  3. 以不同的顺序对副作用进行排序(个人不推荐,这使得对代码的推理变得非常困难)。

关于您的第二个问题,正如您所观察到的,当您点击 return 语句时,Python 函数中的执行结束。在同一代码块中的 return 语句之后放置更多代码毫无意义。

yield 做的事情稍微复杂一些,因为它 return 是一个更接近列表的生成器对象。

代码如下:

def generator_example():
    yield 1
    print "x"
    yield 2

无法与以下相比:

def return_example():
    return 1
    print "x"
    return 2

但更接近于:

def list_example():
    output_list = []
    output_list.append(1)
    print "x"
    output_list.append(2)
    return output_list

generator_examplelist_example 都是 return 可以使用 for 循环迭代的序列。

对代码不相关的评论

虽然下面的部分很奇怪。

  if num_iter > 1 :
    for i in range(1) :
      sim_step = ShangHWSimStep(self)

没有理由使用 for i in range(1),它只循环一次,i 设置为 0。我将删除 for i in range(1) 位,缩进代码并将所有出现的 i 替换为 0,或者更好地重命名 i 以提供更多信息并将其明确设置为 0.