Python中的yield break

74
根据这个问题的答案,在C#中yield break相当于Python中的return。在正常情况下,return确实会停止生成器。但是如果您的函数除了return之外什么都不做,那么您将得到一个None而不是空迭代器,而yield break在C#中返回的就是空迭代器。
def generate_nothing():
    return

for i in generate_nothing():
    print i

你会得到一个 TypeError: 'NoneType' object is not iterable, 但是如果我在 return 之前添加并且从未运行 yield,这个函数将返回我期望的结果。

def generate_nothing():
    if False: yield None
    return

它能用,但感觉很奇怪。你有更好的想法吗?


10
这就是 Python 的工作方式。我认为你甚至不需要最后的 return。Python 不同于 C#,不要期望它们的工作方式一样。 - Chris Lutz
4
没有 yield 的函数不是生成器函数。因此,你的第一个例子只会返回 None,并尝试对其进行迭代。 - Jasmijn
@Jochen:那不是一个生成器。它是一个产生可迭代对象的对象。虽然正如我在phihag的答案中所说,这似乎并不重要。 - Chris Morgan
4个回答

66
def generate_nothing():
    return
    yield

8
这将会在代码检查工具中产生“无法访问的代码”警告。 - congusbongus
1
通常情况下,您希望在生成器给定某些条件时退出,因此使用 if <condition>: return 而不是裸体的 return - Jeyekomon

52

处理这个问题的好方法是引发 StopIteration 异常。当您的迭代器没有要生成的内容并调用 next() 时,就会引发该异常。这也将优雅地中止一个没有执行任何循环内部代码的 for 循环。

例如,假设有一个元组 (0, 1, 2, 3),我想获取重叠的一对 ((0, 1), (1, 2), (2, 3))。我可以这样做:

def pairs(numbers):
    if len(numbers) < 2:
        raise StopIteration

    for i, number in enumerate(numbers[1:]):
        yield numbers[i], number

现在,pairs 安全地处理仅包含一个数字或更少的列表。


32
请注意,从 Python 3.6 开始,这是一个不好的做法,因为随着 PEP 479 的实现,StopIteration 会被转换为 RuntimeError,以避免在使用子生成器时出现问题。 - Tadhg McDonald-Jensen
28
任何版本都是一个不好的想法,只需要“返回”。 - Nick T
15
虽然现在这个代码能够正常工作,但使用return语句会更好;所描述的功能甚至已经包含在return语句的文档中。 - MPlanchard

9
def generate_nothing():
    return iter([])

3
或者 xrange(0)(在Python 3.x中为range(0))。 - Karl Knechtel
2
这两种方式均比 return; yield 快大约两倍。但是请注意,它们将返回 <type 'listiterator'><type 'xrange'> 类型的对象,两者都是 object 的直接子类型 - 因此它们不是生成器。虽然这可能并不重要,仅仅是学术上的兴趣 :-) - Chris Morgan
@Chris:在Python3中,我只发现速度大约快了20-30%左右,虽然从质量上看,它似乎对内存的压力稍微小了一些(并不是一个严格的测试:'z = [list(f()) for _ in range(10**7)]')。 - ninjagecko

4
有趣的是,这两个函数具有相同的字节码。很可能在字节码编译器发现yield关键字时,会设置一个标志为generator
>>> def f():
...   return

>>> def g():
...   if False: yield 
#in Python2 you can use 0 instead of False to achieve the same result


>>> from dis import dis
>>> dis(f)
2           0 LOAD_CONST               0 (None) 
            3 RETURN_VALUE
>>> dis(g)
2           0 LOAD_CONST               0 (None) 
            3 RETURN_VALUE

什么?它们远非相同。请再次检查您的结果。您在某个地方犯了错误。 - Chris Morgan
1
@Chris 是的,Python2不会对其进行优化,因为False不是关键字,可能不是假值。但Python3会进行优化。 - JBernardo
太好了。它甚至不为yield语句生成字节码。 - user780363
@JBernado:嗯,我看到在Python 3中是可能的。一开始我认为如果False被分配了不同的值,那将是一个明显的问题。我忘记了TrueFalse在Python 3中是关键字。并且使用相同的字节码,我推测区别在于f.__code__.co_flags == 67g.__code__.co_flags == 99。这表明第六位标志将其标记为生成器。对于批评你我感到抱歉-但是你现在应该将其标记为Python 3,以便生成相同的字节码。 - Chris Morgan
@Chris 我已经在我的答案中发表了评论 :) 在Python 2中,您可以使用0代替False。 - JBernardo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接