为什么 python 生成器需要 yield?
Why yield is required for python generator?
阅读answer1 and answer2后,yield
的目的仍然不清楚。
在第一种情况下,使用以下函数,
def createGenerator():
mylist = range(3)
for i in mylist:
yield i*i
在下面调用 createGenerator
时,
myGenerator = createGenerator()
应该return类型collections.abc.Generator
类型的对象(如(x*x for x in range(3))
),is-a collections.abc.Iterator
& collections.abc.Iterable
遍历 myGenerator
对象并获取第一个值(0
),
next(myGenerator)
实际上会使 createGenerator
函数的 for
循环在内部调用 __iter__(myGenerator)
并检索 collections.abc.Iterator
类型对象( obj
(say) )然后调用 __next__(obj)
以获取第一个值 (0
),然后使用 yield
关键字
暂停 for
循环
如果这个理解(以上)是正确的,那么,
然后,执行以下语法(第二种情况),
def createGenerator():
return (x*x for x in range(3))
myGen = createGenerator() # returns collections.abc.Generator type object
next(myGen) # next() must internally invoke __next__(__iter__(myGen)) to provide first value(0) and no need to pause
是否足以满足相同的目的(如上)并且看起来更具可读性?两种语法记忆效率都不是吗?如果是,那么什么时候应该使用 yield
关键字?是否存在必须使用 yield
的情况?
尝试在没有 yield
的情况下执行此操作
def func():
x = 1
while 1:
y = yield x
x += y
f = func()
f.next() # Returns 1
f.send(3) # returns 4
f.send(10) # returns 14
生成器有两个重要特性:
生成器的某个状态(x
的值)。由于这种状态,此生成器最终可以 return 任意数量的结果,而无需使用大量内存。
由于状态和 yield
,我们可以为生成器提供用于计算下一个输出的信息。当我们调用 send
.
时,该值被分配给 y
如果没有 yield
,我认为这是不可能的。
也就是说,我很确定你可以用生成器函数做的任何事情也可以用 class.
来完成
这是一个 class 的例子,它做的事情完全相同(python 2 语法):
class MyGenerator(object):
def __init__(self):
self.x = 1
def next(self):
return self.x
def send(self, y):
self.x += y
return self.next()
我没有实施 __iter__
但很明显它应该如何工作。
将收益率视为 "lazy return"。在你的第二个例子中,你的函数不是 return a "generator of values",而是一个完全评估的值列表。根据用例,这可能是完全可以接受的。 Yield 在处理大批量流式数据或处理不是立即可用的数据(想想异步操作)时很有用。
生成器函数和生成器理解基本相同——都生成生成器对象:
In [540]: def createGenerator(n):
...: mylist = range(n)
...: for i in mylist:
...: yield i*i
...:
In [541]: g = createGenerator(3)
In [542]: g
Out[542]: <generator object createGenerator at 0xa6b2180c>
In [545]: gl = (i*i for i in range(3))
In [546]: gl
Out[546]: <generator object <genexpr> at 0xa6bbbd7c>
In [547]: list(g)
Out[547]: [0, 1, 4]
In [548]: list(gl)
Out[548]: [0, 1, 4]
g
和gl
的属性相同;产生相同的值; 运行以同样的方式出来。
就像列表推导式一样,有些事情您可以在显式循环中使用推导式做不到。但是,如果理解能起到作用,就使用它。生成器在 2.2 版左右的某个时候被添加到 Python。生成器理解较新(并且可能使用相同的底层机制)。
在 Py3 range
或 Py2 xrange
中一次生成一个值,而不是整个列表。它是一个 range
对象,而不是生成器,但工作方式大致相同。 Py3 在其他方面对此进行了扩展,例如字典 keys
和 map
。有时这是为了方便,有时我忘记将它们包装在 list()
.
中
yield
可以更详细,允许调用者 'feedback'。例如
In [564]: def foo(n):
...: i = 0
...: while i<n:
...: x = yield i*i
...: if x is None:
...: i += 1
...: else:
...: i = x
...:
In [576]: f = foo(3)
In [577]: next(f)
Out[577]: 0
In [578]: f.send(-3) # reset the counter
Out[578]: 9
In [579]: list(f)
Out[579]: [4, 1, 0, 1, 4]
我认为生成器运行的方式是创建时使用代码和初始状态初始化对象。 next()
运行 达到 yield
和 returns 该值。下一个 next()
让它再次旋转,直到它达到 yield
,依此类推,直到它达到 stop iteration
条件。所以它是一个维护内部状态的函数,可以用 next
或 for
迭代重复调用。使用 send
和 yield from
等等 generators
可以更加复杂。
通常是一个函数 运行s 直到完成,并且 returns。对该函数的下一次调用与第一次调用无关——除非您使用全局变量或容易出错的默认值。
https://www.python.org/dev/peps/pep-0289/ 是生成器表达式的 PEP,来自 v 2.4。
This PEP introduces generator expressions as a high performance, memory efficient generalization of list comprehensions [1] and generators [2] .
https://www.python.org/dev/peps/pep-0255/ 生成器的 PEP,v.2.2
关于将数据 send
放入带有 yield 的生成器的能力,已经有了很好的答案。关于可读性方面的考虑,虽然简单、直接的转换可以作为生成器表达式更具可读性:
(x + 1 for x in iterable if x%2 == 1)
某些操作使用完整的生成器定义更容易阅读和理解。某些情况很难适应生成器表达式,请尝试以下操作:
>>> x = ['arbitrarily', ['nested', ['data'], 'can', [['be'], 'hard'], 'to'], 'reach']
>>> def flatten_list_of_list(lol):
... for l in lol:
... if isinstance(l, list):
... yield from flatten_list_of_list(l)
... else:
... yield l
...
>>> list(flatten_list_of_list(x))
['arbitrarily', 'nested', 'data', 'can', 'be', 'hard', 'to', 'reach']
当然,您可以使用 lambda
s 来破解适合单行的解决方案以实现递归,但这将是一团乱麻。现在假设我有一些涉及 list
和 dict
的任意嵌套数据结构,并且我有处理这两种情况的逻辑......你明白我的意思了。
阅读answer1 and answer2后,yield
的目的仍然不清楚。
在第一种情况下,使用以下函数,
def createGenerator():
mylist = range(3)
for i in mylist:
yield i*i
在下面调用 createGenerator
时,
myGenerator = createGenerator()
应该return类型collections.abc.Generator
类型的对象(如(x*x for x in range(3))
),is-a collections.abc.Iterator
& collections.abc.Iterable
遍历 myGenerator
对象并获取第一个值(0
),
next(myGenerator)
实际上会使 createGenerator
函数的 for
循环在内部调用 __iter__(myGenerator)
并检索 collections.abc.Iterator
类型对象( obj
(say) )然后调用 __next__(obj)
以获取第一个值 (0
),然后使用 yield
关键字
for
循环
如果这个理解(以上)是正确的,那么,
然后,执行以下语法(第二种情况),
def createGenerator():
return (x*x for x in range(3))
myGen = createGenerator() # returns collections.abc.Generator type object
next(myGen) # next() must internally invoke __next__(__iter__(myGen)) to provide first value(0) and no need to pause
是否足以满足相同的目的(如上)并且看起来更具可读性?两种语法记忆效率都不是吗?如果是,那么什么时候应该使用 yield
关键字?是否存在必须使用 yield
的情况?
尝试在没有 yield
def func():
x = 1
while 1:
y = yield x
x += y
f = func()
f.next() # Returns 1
f.send(3) # returns 4
f.send(10) # returns 14
生成器有两个重要特性:
生成器的某个状态(
x
的值)。由于这种状态,此生成器最终可以 return 任意数量的结果,而无需使用大量内存。由于状态和
yield
,我们可以为生成器提供用于计算下一个输出的信息。当我们调用send
. 时,该值被分配给
y
如果没有 yield
,我认为这是不可能的。
也就是说,我很确定你可以用生成器函数做的任何事情也可以用 class.
这是一个 class 的例子,它做的事情完全相同(python 2 语法):
class MyGenerator(object):
def __init__(self):
self.x = 1
def next(self):
return self.x
def send(self, y):
self.x += y
return self.next()
我没有实施 __iter__
但很明显它应该如何工作。
将收益率视为 "lazy return"。在你的第二个例子中,你的函数不是 return a "generator of values",而是一个完全评估的值列表。根据用例,这可能是完全可以接受的。 Yield 在处理大批量流式数据或处理不是立即可用的数据(想想异步操作)时很有用。
生成器函数和生成器理解基本相同——都生成生成器对象:
In [540]: def createGenerator(n):
...: mylist = range(n)
...: for i in mylist:
...: yield i*i
...:
In [541]: g = createGenerator(3)
In [542]: g
Out[542]: <generator object createGenerator at 0xa6b2180c>
In [545]: gl = (i*i for i in range(3))
In [546]: gl
Out[546]: <generator object <genexpr> at 0xa6bbbd7c>
In [547]: list(g)
Out[547]: [0, 1, 4]
In [548]: list(gl)
Out[548]: [0, 1, 4]
g
和gl
的属性相同;产生相同的值; 运行以同样的方式出来。
就像列表推导式一样,有些事情您可以在显式循环中使用推导式做不到。但是,如果理解能起到作用,就使用它。生成器在 2.2 版左右的某个时候被添加到 Python。生成器理解较新(并且可能使用相同的底层机制)。
在 Py3 range
或 Py2 xrange
中一次生成一个值,而不是整个列表。它是一个 range
对象,而不是生成器,但工作方式大致相同。 Py3 在其他方面对此进行了扩展,例如字典 keys
和 map
。有时这是为了方便,有时我忘记将它们包装在 list()
.
yield
可以更详细,允许调用者 'feedback'。例如
In [564]: def foo(n):
...: i = 0
...: while i<n:
...: x = yield i*i
...: if x is None:
...: i += 1
...: else:
...: i = x
...:
In [576]: f = foo(3)
In [577]: next(f)
Out[577]: 0
In [578]: f.send(-3) # reset the counter
Out[578]: 9
In [579]: list(f)
Out[579]: [4, 1, 0, 1, 4]
我认为生成器运行的方式是创建时使用代码和初始状态初始化对象。 next()
运行 达到 yield
和 returns 该值。下一个 next()
让它再次旋转,直到它达到 yield
,依此类推,直到它达到 stop iteration
条件。所以它是一个维护内部状态的函数,可以用 next
或 for
迭代重复调用。使用 send
和 yield from
等等 generators
可以更加复杂。
通常是一个函数 运行s 直到完成,并且 returns。对该函数的下一次调用与第一次调用无关——除非您使用全局变量或容易出错的默认值。
https://www.python.org/dev/peps/pep-0289/ 是生成器表达式的 PEP,来自 v 2.4。
This PEP introduces generator expressions as a high performance, memory efficient generalization of list comprehensions [1] and generators [2] .
https://www.python.org/dev/peps/pep-0255/ 生成器的 PEP,v.2.2
关于将数据 send
放入带有 yield 的生成器的能力,已经有了很好的答案。关于可读性方面的考虑,虽然简单、直接的转换可以作为生成器表达式更具可读性:
(x + 1 for x in iterable if x%2 == 1)
某些操作使用完整的生成器定义更容易阅读和理解。某些情况很难适应生成器表达式,请尝试以下操作:
>>> x = ['arbitrarily', ['nested', ['data'], 'can', [['be'], 'hard'], 'to'], 'reach']
>>> def flatten_list_of_list(lol):
... for l in lol:
... if isinstance(l, list):
... yield from flatten_list_of_list(l)
... else:
... yield l
...
>>> list(flatten_list_of_list(x))
['arbitrarily', 'nested', 'data', 'can', 'be', 'hard', 'to', 'reach']
当然,您可以使用 lambda
s 来破解适合单行的解决方案以实现递归,但这将是一团乱麻。现在假设我有一些涉及 list
和 dict
的任意嵌套数据结构,并且我有处理这两种情况的逻辑......你明白我的意思了。