Python yield return区别

一、说明

return一直中,每中语言中其没没有很大差别,就不多说了。(shell语言return的是退出状态,可能差别是比较大的,感兴趣可参见“Linux Shell函数定义与调用”)

最早看到yield应该是哪们语言用来调整什么线程优先级的,记不清了,不过那里的yield和python中的yield应该功能有区别。

python中最早看到yield应该是使用scrapy框架写爬虫的时候,之前也有去看yiled的用法,总记不太住。今天又去看了一下,基本上来就是讲些斐波那契数列的烦的要死,自己写段程序研究了一下,这里记一下。

二、return和yield的异同

共同点:return和yield都用来返回值;在一次性地返回所有值场景中return和yield的作用是一样的。

不同点:如果要返回的数据是通过for等循环生成的迭代器类型数据(如列表、元组),return只能在循环外部一次性地返回,yeild则可以在循环内部逐个元素返回。下边我们举例说明这个不同点。

三、实例说明

3.1 return版本

示例代码如下:

class TestYield:
    def gen_iterator(self):
        result_list = []
        for j in range(3):
            print(f"gen_iterator-{j}")
            result_list.append(j)
        # return在循环的外部,待变量完全生成后一次性返回
        return result_list

    def call_gen_iterator(self):
        # 执行下边这句后result_list直接是完成的结果[0,1,2]
        result_list = self.gen_iterator()
        for i in result_list:
            print(f"call_gen_iterator-{i}")

if __name__ == "__main__":
    obj = TestYield()
    obj.call_gen_iterator()

执行结果如下,可以看到一次性执行完下层函数,生成完整的迭代器类型返回值result_list,一次性返回给上层函数:

Python yield return区别

3.2 yield版本

示例代码如下:

class TestYield:
    def gen_iterator(self):
        for j in range(3):
            print(f"do_something-{j}")
            # yield在for循环内部
            yield j

    def call_gen_iterator(self):
        # yield并不是直接返回[0,1,2],执行下边这句后result_list什么值都没有
        result_list = self.gen_iterator()
        # i每请求一个数据,才会触发gen_iterator生成一个数据
        for i in result_list:
            print(f"call_gen_iterator-{i}")

if __name__ == "__main__":
    obj = TestYield()
    obj.call_gen_iterator()

执行结果如下,可以看到上下层函数是交替进行的,即上层函数请求迭代一个值下层函数才生成一个值并立即返回这个值:

Python yield return区别

3.3 yield的意义

从上边两个小节可以看到,虽然return和yield两者执行的顺序有区别,但整个要做的事情是一样的,所以使用yield并不会比return快,甚至我们可以猜测由于yield总发生上下文切换在速度上还会慢一些,所以速度不是yield的意义。

他们的主要区别是yiled要迭代到哪个元素那个元素才即时地生成,而return要用一个中间变量result_list保存返回值,当result_list的长度很长且每个组成元素内容很大时将会耗费比较大的内存,此时yield相对return才有优势。

四、yield和return嵌套使用

Python yield return区别

class TestYield:
    def gen_iterator(self):
        for j in range(3):
            print(f"do_something-{j}")
            # yield在for循环内部
            yield j

    def gen_iterator_middle(self):
        print(f"gen_iterator_middle")
        # 返回的是迭代器的句柄,所以加一层return不影响是可以理解的
        return self.gen_iterator()

    def call_gen_iterator(self):
        # yield并不是直接返回[0,1,2],执行下边这句后result_list什么值都没有
        result_list = self.gen_iterator_middle()
        # i每请求一个数据,才会触发gen_iterator生成一个数据
        for i in result_list:
            print(f"call_gen_iterator-{i}")

if __name__ == "__main__":
    obj = TestYield()
    obj.call_gen_iterator()

View Code

Python yield return区别

                           python中的yield和return的区别

return返回的是一个list列表,而yield每次调用只返回一个数值,毫无疑问,使用return空间开销比较大,尤其是操作巨量数据的时候,操作一个大列表时间开销也会得不偿失
yield 生成器相比 return一次返回所有结果的优势:

(1)反应更迅速

(2)更节省空间

(3)使用更灵活
yield和return的关系和区别:带yield的函数是一个生成器,而不是一个函数了,这个生成器有一个函数就是next函数,next就相当于“下一步”生成哪个数,这一次的next开始的地方是接着上一次的next停止的地方执行的,所以调用next的时候,生成器并不会从foo函数的开始执行,只是接着上一步停止的地方开始,然后遇到yield后,return出要生成的数,此步就结束。
可以看出,yield 输出的是一个对象,相当于是一个容器,想取什么数据就取出什么,而return 只会返回一个值,且return后面的代码不会执行。

迭代器Iterables

迭代器就是你创建一个列表,你可以一个个的读取。

lists,strings,files 等都是可以迭代的,只要你可以用for ... in ...,但是你必须把它们的值放到内存里,当它们有很多值时就会消耗太多的内存.

lists = [1,2,3,4]
mys = [x*x for x in range(3)]
for i in mylist:
    print(i)

生成器generator

生成器也是迭代器,即迭代器包括生成器,但是你只能迭代他们一次,因为他们不是全部在内存中,他们只有在调用的时候在内存中生成。

mygenerator = (x*x for x in range(3))
for i in mygenerator:
    print(i)

但是生成器是用()而不是用[]
还有你不能用for i in mygenerator第二次调用生成器
因为其计算完就丢弃。

Yield

Yield的用法和关键字return差不多,下面的函数将会返回一个生成器

def createGenerator():
...    mylist = range(3)
...    for i in mylist:
...        yield i*i
...
>>> mygenerator = createGenerator() # 创建生成器
>>> print(mygenerator) # mygenerator is an object!
<generator object createGenerator at 0xb7555c34>
>>> for i in mygenerator:
...     print(i)

当for语句第一次调用函数里返回的生成器对象,函数里的代码就开始运作,直到碰到yield,然后会返回本次循环的第一个返回值.所以下一次调用也将运行一次循环然后返回下一个值,直到没有值可以返回.

一旦函数运行并没有碰到yeild语句就认为生成器已经为空了.原因有可能是循环结束或者没有满足if/else之类的.
extend()是一个列表对象的方法,它可以把一个迭代对象添加进列表

生成器的好处

  • 你不需要读这个值两次
  • 你能得到许多孩子节点但是你不希望他们全部存入内存.
  • 这种方法之所以能很好的运行是因为Python不关心方法的参数是不是一个列表.它只希望接受一个迭代器,所以不管是strings,lists,tuples或者generators都可以!
  • 这种方法叫做duck typing,

控制迭代器穷尽

class Bank(): # 让我们建个银行,生产许多ATM
...    crisis = False
...    def create_atm(self):
...        while not self.crisis:
...            yield "$100"
>>> hsbc = Bank() # 当一切就绪了你想要多少ATM就给你多少
>>> corner_street_atm = hsbc.create_atm()
>>> print(corner_street_atm.next())
$100
>>> print(corner_street_atm.next())
$100
>>> print([corner_street_atm.next() for cash in range(5)])
['$100', '$100', '$100', '$100', '$100']
>>> hsbc.crisis = True # cao,经济危机来了没有钱了!
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> wall_street_atm = hsbc.create_atm() # 对于其他ATM,它还是True
>>> print(wall_street_atm.next())
<type 'exceptions.StopIteration'>
>>> hsbc.crisis = False # 麻烦的是,尽管危机过去了,ATM还是空的
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> brand_new_atm = hsbc.create_atm() # 只能重新新建一个bank了
>>> for cash in brand_new_atm:
...    print cash

迭代进阶 itertools

设置起点和步长

itertools.count(start=0, step=1)

from itertools import *

for i in izip(count(1), ['a', 'b', 'c']):
    print i

(1, 'a')
(2, 'b')
(3, 'c')

itertools.cycle(iterable)

from itertools import *

i = 0
for item in cycle(['a', 'b', 'c']):
    i += 1
    if i == 10:
        break
    print (i, item)

(1, 'a')
(2, 'b')
(3, 'c')
(4, 'a')
(5, 'b')
(6, 'c')
(7, 'a')
(8, 'b')
(9, 'c')

itertools.repeat(object[, times])

from itertools import *

for i in repeat('over-and-over', 5):
    print i

over-and-over
over-and-over
over-and-over
over-and-over
over-and-over

itertools.chain(*iterables)
将多个迭代器作为参数, 但只返回单个迭代器, 它产生所有参数迭代器的内容, 就好像他们是来自于一个单一的序列.

from itertools import *

for i in chain([1, 2, 3], ['a', 'b', 'c']):
    print i
1
2
3
a
b
c

itertools.groupby(iterable[, key])

from itertools import groupby
qs = [{'date' : 1},{'date' : 2}]
[(name, list(group)) for name, group in itertools.groupby(qs, lambda p:p['date'])]

Out[77]: [(1, [{'date': 1}]), (2, [{'date': 2}])]


>>> from itertools import *
>>> a = ['aa', 'ab', 'abc', 'bcd', 'abcde']
>>> for i, k in groupby(a, len):
...     print i, list(k)
...
2 ['aa', 'ab']
3 ['abc', 'bcd']
5 ['abcde']

itertools.permutations

horses = [1, 2, 3, 4]
>>> races = itertools.permutations(horses)
>>> print(races)
<itertools.permutations object at 0xb754f1dc>
>>> print(list(itertools.permutations(horses)))
[(1, 2, 3, 4),
 (1, 2, 4, 3),
 (1, 3, 2, 4),
 ...

更多的见
(http://www.wklken.me/posts/2013/08/20/python-extra-itertools.html)