混合产量和return。 `产量[cand]; return` 对比 `return [[cand]]`。为什么它们会导致不同的输出?
Mixing yield and return. `yield [cand]; return` vs `return [[cand]]`. Why do they lead to different output?
为什么
yield [cand]
return
导致 output/behavior 不同于
return [[cand]]
最小可行示例
- 使用递归
- 使用
yield [1]; return
的版本的输出与使用 return [[1]]
的版本的输出不同
def foo(i):
if i != 1:
yield [1]
return
yield from foo(i-1)
def bar(i):
if i != 1:
return [[1]]
yield from bar(i-1)
print(list(foo(1))) # [[1]]
print(list(bar(1))) # []
最小可行反例
- 不使用递归
- 使用
yield [1]; return
的版本的输出与使用 return [[1]]
的版本的输出相同
def foo():
yield [1]
return
def foofoo():
yield from foo()
def bar():
return [[1]]
def barbar():
yield from bar()
print(list(foofoo())) # [[1]]
print(list(barbar())) # [[1]]
完整上下文
我正在解决 Leetcode #39: Combination Sum 并且想知道为什么一个解决方案有效,而另一个无效:
工作解决方案
from functools import cache # requires Python 3.9+
class Solution:
def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
@cache
def helper(targ, i=0):
if i == N or targ < (cand := candidates[i]):
return
if targ == cand:
yield [cand]
return
for comb in helper(targ - cand, i):
yield comb + [cand]
yield from helper(targ, i+1)
N = len(candidates)
candidates.sort()
yield from helper(target)
无效的解决方案
from functools import cache # requires Python 3.9+
class Solution:
def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
@cache
def helper(targ, i=0):
if i == N or targ < (cand := candidates[i]):
return
if targ == cand:
return [[cand]]
for comb in helper(targ - cand, i):
yield comb + [cand]
yield from helper(targ, i+1)
N = len(candidates)
candidates.sort()
yield from helper(target)
输出
关于以下输入
candidates = [2,3,6,7]
target = 7
print(Solution().combinationSum(candidates, target))
工作解决方案正确打印
[[3,2,2],[7]]
当非工作解决方案打印时
[]
我想知道为什么 yield [cand]; return
有效,但 return [[cand]]
无效。
在生成器函数中,return
仅定义与隐式引发的 StopIteration
异常关联的值,以指示迭代器已耗尽。它不是在迭代期间产生的,并且大多数迭代构造(例如 for
循环)有意忽略 StopIteration
异常(这意味着循环结束,你不关心是否有人将随机垃圾附加到消息中只是意味着“我们完成了”)。
例如,尝试:
>>> def foo():
... yield 'onlyvalue' # Existence of yield keyword makes this a generator
... return 'returnvalue'
...
>>> f = foo() # Makes a generator object, stores it in f
>>> next(f) # Pull one value from generator
'onlyvalue'
>>> next(f) # There is no other yielded value, so this hits the return; iteration over
--------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
...
StopIteration: 'returnvalue'
如您所见,您的 return
值在某种意义上确实得到了“returned”(它并没有完全被丢弃),但是它从来没有被任何正常迭代的东西看到,所以它在很大程度上是无用的.除了涉及将生成器用作协程的罕见情况(您在生成器的实例上使用 .send()
和 .throw()
并使用 next(genobj)
手动推进它),return 值不会看到发电机。
总之,你要选一个:
- 在一个函数中使用
yield
anywhere,并且它是一个生成器(无论是否是特定的代码路径调用曾经达到 yield
) 并且 return
刚刚结束生成(同时可能在 StopIteration
异常中隐藏了一些数据)。无论你做什么,调用生成器函数“returns”一个新的生成器对象(你可以循环直到耗尽),它永远不会 return 生成器函数内部计算的原始值(它甚至 begin 运行 直到你至少循环一次)。
- 不要使用
yield
,return
会按预期工作(因为它不是生成器函数)。
作为解释 return
值在正常循环结构中发生的情况的示例,这是 for x in gen():
有效扩展为 C 优化版本的内容:
__unnamed_iterator = iter(gen())
while True:
try:
x = next(__unnamed_iterator)
except StopIteration: # StopIteration caught here without inspecting it
break # Loop ends, StopIteration exception cleaned even from sys.exc_info() to avoid possible reference cycles
# body of loop goes here
# Outside of loop, there is no StopIteration object left
如您所见,for
循环的扩展形式 有 寻找 StopIteration
来指示循环结束,但它不使用它。对于任何不是生成器的东西, StopIteration
从来没有任何关联值; for
循环无法报告它们,即使它报告了(当它被告知迭代结束时它必须结束循环,并且 StopIteration
的参数显然不是迭代值的一部分) .消耗生成器的任何其他东西(例如调用 list
)与 for
循环大致相同,以相同的方式忽略 StopIteration
; nothing except code that specifically expects generators (相对于更通用的迭代器和迭代器)将 ever 打扰检查 StopIteration
对象(在 C 层,大多数迭代器甚至不生成 StopIteration
对象的优化;它们 return NULL
并保留设置异常空,所有使用事物的迭代器协议都知道等同于 returning NULL
并设置一个 StopIteration
对象,因此对于除了生成器之外的任何东西,甚至没有异常检查当时)。
为什么
yield [cand]
return
导致 output/behavior 不同于
return [[cand]]
最小可行示例
- 使用递归
- 使用
yield [1]; return
的版本的输出与使用return [[1]]
的版本的输出不同
def foo(i):
if i != 1:
yield [1]
return
yield from foo(i-1)
def bar(i):
if i != 1:
return [[1]]
yield from bar(i-1)
print(list(foo(1))) # [[1]]
print(list(bar(1))) # []
最小可行反例
- 不使用递归
- 使用
yield [1]; return
的版本的输出与使用return [[1]]
的版本的输出相同
def foo():
yield [1]
return
def foofoo():
yield from foo()
def bar():
return [[1]]
def barbar():
yield from bar()
print(list(foofoo())) # [[1]]
print(list(barbar())) # [[1]]
完整上下文
我正在解决 Leetcode #39: Combination Sum 并且想知道为什么一个解决方案有效,而另一个无效:
工作解决方案
from functools import cache # requires Python 3.9+
class Solution:
def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
@cache
def helper(targ, i=0):
if i == N or targ < (cand := candidates[i]):
return
if targ == cand:
yield [cand]
return
for comb in helper(targ - cand, i):
yield comb + [cand]
yield from helper(targ, i+1)
N = len(candidates)
candidates.sort()
yield from helper(target)
无效的解决方案
from functools import cache # requires Python 3.9+
class Solution:
def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
@cache
def helper(targ, i=0):
if i == N or targ < (cand := candidates[i]):
return
if targ == cand:
return [[cand]]
for comb in helper(targ - cand, i):
yield comb + [cand]
yield from helper(targ, i+1)
N = len(candidates)
candidates.sort()
yield from helper(target)
输出
关于以下输入
candidates = [2,3,6,7]
target = 7
print(Solution().combinationSum(candidates, target))
工作解决方案正确打印
[[3,2,2],[7]]
当非工作解决方案打印时
[]
我想知道为什么 yield [cand]; return
有效,但 return [[cand]]
无效。
在生成器函数中,return
仅定义与隐式引发的 StopIteration
异常关联的值,以指示迭代器已耗尽。它不是在迭代期间产生的,并且大多数迭代构造(例如 for
循环)有意忽略 StopIteration
异常(这意味着循环结束,你不关心是否有人将随机垃圾附加到消息中只是意味着“我们完成了”)。
例如,尝试:
>>> def foo():
... yield 'onlyvalue' # Existence of yield keyword makes this a generator
... return 'returnvalue'
...
>>> f = foo() # Makes a generator object, stores it in f
>>> next(f) # Pull one value from generator
'onlyvalue'
>>> next(f) # There is no other yielded value, so this hits the return; iteration over
--------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
...
StopIteration: 'returnvalue'
如您所见,您的 return
值在某种意义上确实得到了“returned”(它并没有完全被丢弃),但是它从来没有被任何正常迭代的东西看到,所以它在很大程度上是无用的.除了涉及将生成器用作协程的罕见情况(您在生成器的实例上使用 .send()
和 .throw()
并使用 next(genobj)
手动推进它),return 值不会看到发电机。
总之,你要选一个:
- 在一个函数中使用
yield
anywhere,并且它是一个生成器(无论是否是特定的代码路径调用曾经达到yield
) 并且return
刚刚结束生成(同时可能在StopIteration
异常中隐藏了一些数据)。无论你做什么,调用生成器函数“returns”一个新的生成器对象(你可以循环直到耗尽),它永远不会 return 生成器函数内部计算的原始值(它甚至 begin 运行 直到你至少循环一次)。 - 不要使用
yield
,return
会按预期工作(因为它不是生成器函数)。
作为解释 return
值在正常循环结构中发生的情况的示例,这是 for x in gen():
有效扩展为 C 优化版本的内容:
__unnamed_iterator = iter(gen())
while True:
try:
x = next(__unnamed_iterator)
except StopIteration: # StopIteration caught here without inspecting it
break # Loop ends, StopIteration exception cleaned even from sys.exc_info() to avoid possible reference cycles
# body of loop goes here
# Outside of loop, there is no StopIteration object left
如您所见,for
循环的扩展形式 有 寻找 StopIteration
来指示循环结束,但它不使用它。对于任何不是生成器的东西, StopIteration
从来没有任何关联值; for
循环无法报告它们,即使它报告了(当它被告知迭代结束时它必须结束循环,并且 StopIteration
的参数显然不是迭代值的一部分) .消耗生成器的任何其他东西(例如调用 list
)与 for
循环大致相同,以相同的方式忽略 StopIteration
; nothing except code that specifically expects generators (相对于更通用的迭代器和迭代器)将 ever 打扰检查 StopIteration
对象(在 C 层,大多数迭代器甚至不生成 StopIteration
对象的优化;它们 return NULL
并保留设置异常空,所有使用事物的迭代器协议都知道等同于 returning NULL
并设置一个 StopIteration
对象,因此对于除了生成器之外的任何东西,甚至没有异常检查当时)。