zoukankan      html  css  js  c++  java
  • python生成器

    生成器(generator)指代的是生成器对象,它可以由生成器表达式得到,也可以使用yield关键字得到一个生成器函数,调用这个生成器函数就可以得到一个生成器对象。另外,通过数据的转换也可以获取生成器。

    生成器对象是一个可迭代对象,它是一个迭代器。生成器的作用是延迟计算,惰性求值。

    生成器表达式产生生成器对象

    #生成器表达式产生生成器
    a =(x**2 for x in range(5))
    print(type(a))
    print(next(a))
    print(next(a))
    print(next(a))
    print(next(a))
    
    结果为:
    <class 'generator'>
    0
    1
    4
    9

    生成器函数

    生成器函数也就是函数体中包含yield关键字的函数,这个函数会返回一个生成器对象!

    def inc():
        for i in range(5):
            yield i 
    
    print(type(inc))
    print(type(inc()))
    
    x=inc()
    print(type(x))
    print(next(x))
    
    for m in x:
        print(m,"**")
    
    for i in x:#因为x为生成器对象,所以它只能被遍历一次。再次遍历不会被执行了。
        print(i,"*")
    
    结果为:
    
    <class 'function'>
    <class 'generator'>
    <class 'generator'>
    0
    1 **
    2 **
    3 **
    4 **

    由上面的例子可以发现,普通的函数调用,函数会立即执行完毕,但是生成器函数不会,它可以使用next函数执行多次,直到最后。

    def func():
        print("111")
        yield 222
    gener = func() 
    ret = gener.__next__()
    #print(ret)
    
    结果为:
    111

    而生成器函数等价于生成器表达式,只不过生成器函数可以更加的复杂。比如上面的生成器函数可以写成下面的生成器表达式。

    y = (i for i in range(5))
    print(type(y))
    print(next(y))
    print(next(y))
    
    结果为:
    <class 'generator'>
    0
    1

    在生成器函数中,可以使用多个yield语句,执行一次后会暂停执行,然后把yield表达式的值返回。函数如果再次执行的话,会执行到下一个yield语句,在生成器函数中, return依然可以终止程序的运行,单return语句的返回值不能被获取到。

    在生成器函数中,return会导致无法继续获得下一个值,这个时候会抛出stopiteration异常,而如果函数没有显示的return语句,如果生成器函数执行到结尾,一样会抛出stopiteration异常。

    def gen():
        print("line 1 ")
        yield 1
        print("line 2")
        yield 2
        print("line 3")
        return 3
    
    next(gen())#line 1
    next(gen())#line 1都是line1因为这不是同一个生成器对象,第二次执行函数,又返回了一个生成器对象。
    
    g =gen()
    print(next(g))
    print(next(g))
    print(next(g))
    
    结果为:
    line 1 
    line 1 
    line 1 
    1
    line 2
    2
    line 3
    ---------------------------------------------------------------------------
    StopIteration                             Traceback (most recent call last)
    <ipython-input-16-3c0007202311> in <module>
         13 print(next(g))
         14 print(next(g))
    ---> 15 print(next(g))
    
    StopIteration: 3
    
    
    def gen():
        print("line 1 ")
        yield 1
        print("line 2")
        yield 2
        print("line 3")
        return 3
    
    next(gen())
    next(gen())
    
    g =gen()
    print(next(g))
    print(next(g))
    print(next(g, 'End')) #没有元素了,给个缺省值
    print(next(g, 'End')) 
    
    结果为:
    
    line 1 
    line 1 
    line 1 
    1
    line 2
    2
    line 3
    End
    End

    所以,生成器函数就是包含yield语句的函数,而生成器函数生成“生成器”的时候,生成器函数的函数体并不会立即执行,它需要next函数,有点拨一下转一下的意思,next(generator)会从函数的当前位置向后执行到碰到的第一个yield语句,这个时候有值会弹出值,并且暂停函数的执行。

    再次调用next函数,重复上面的步骤。,而如果没有多余的yield语句被执行,如果再次调用next函数,就会抛出stopiteration异常。

    生成器应用

    现在有一个需求,比如我像一个服装厂订购了100件衣服,这个时候服装厂全部给我生成好了。比如下面这个代码。

    def cloth():
        lst = []
        for i in range(10):
            lst.append("cloth"+str(i))
        return lst
    cloth()
    
    结果为:
    ['cloth0',
     'cloth1',
     'cloth2',
     'cloth3',
     'cloth4',
     'cloth5',
     'cloth6',
     'cloth7',
     'cloth8',
     'cloth9']

    但是,现在我还没有那么多的学习,一次性给我那么多,我根本没法用。所以,最好的我要一件,你给我一件。这个时候就需要用到生成器了。

    def cloth():
        lst = []
        for i in range(10):
             yield "cloth"+str(i)
    
    a = cloth()
    print(a.__next__())
    print(a.__next__())
    print(a.__next__())
    
    结果为:
    cloth0
    cloth1
    cloth2

    所以,上面的第一种就是一次性全部拿出来,会占用内存,第二种生成器,一次就一个,用多少生成多少,用next函数一个一个的拿,然后指向下一个。

    生成器还可以用在诸如无限循环,计算器,处理递归等问题上,应用最广泛的是协程。

    #生成器处理无限循环,就不会陷入死循环,它拨一下,转一下。
    def counter():
        i = 0
        while True:
            i+=1
            yield i
    
    def inc(a):
        return next(a)
    
    c = counter()
    print(inc(c))
    print(inc(c))
    for i in range(10):
        print(inc(c))
    
    结果为:
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    应该注意的是,如果上面的代码写成下面这样,每次就会产生一个新的“生成器”,达不到拨一下转一下的效果。

    def counter():
        i = 0
        while True:
            i+=1
            yield i
    
    def inc():
        a = counter()
        return next(a)
    
    print(inc())
    print(inc())
    print(inc())
    
    结果为:
    1
    1
    1

    下面用生成器配合无限循环写一个计数器。

    def inc():
        def counter():
            i = 0
            while True:
                i+=2
                yield i
        c = counter()
        return lambda :next(c)
    
    foo = inc()
    print(foo())
    print(foo())
    
    结果为:
    2
    4

    这个例子和上面无限循环差不多,都是一个意思,只是换了一种写法,同时这个例子用到了lambda表达式,而return返回的是一个匿名函数。它也等价于下面的代码:

    def inc():
        def counter():
            i = 0
            while True:
                i+=2
                yield i
        c = counter()
        def _inct():
            return next(c)
        return _inct
    
    foo = inc()
    print(foo())
    print(foo())
    print(foo())

    生成器同样可以用来处理递归的问题,比如下面这个例子实现斐波拉契数列。可以想生成多少就生成多少。

    def fib():
        x = 0
        y = 1
        while True:
            yield y
            x,y=y,x+y
    
    foo = fib()
    for _ in range(5):#生成5个
        print(next(foo))
    
    for _ in range(5):#再生成5个
        print(next(foo))
    
    结果为:
    
    1
    1
    2
    3
    5
    8
    13
    21
    34
    55

    上面的这段代码等价于下面的这段代码。

    pre = 0 
    cur = 1
    print(pre,cur,end=" ")
    
    def fib1(n,pre = 0,cur=1):
        pre,cur=cur,pre+cur
        print(cur,end=" ")
        if n ==2:
            return
        fib1(n-1,pre,cur)
    fib1(11)
    
    结果为:
    0 1 1 2 3 5 8 13 21 34 55 89 

    生成器还有一个高级用法,那就是协程coroutine,协程比进程、线程轻量级,它是在用户空间调度函数的一种实现,在Python 3 中,asyncio就是协程实现,已经加入到标准库,同时在Python3.5中,使用asyncawait关键字直接原生支持协程。

    协程调度器实现原理为:有两个生成器A,B,next(a)后,A执行到了yield语句暂停,然后去执行next(B),B执行到yield语句也暂停,然后再次调用next(A),再调用next(B),周而复始,就实现了调度的效果。

    同时应该注意,协程是一种非抢占式的调度。

    yield from

    yield fromީ是Python3.3出现的新语法,yield from iteratle 是 for item in iterable:yield item形式的语法糖。

    def inc():
        for i in range(1000):
            yield i
    foo = inc()
    print(next(foo))
    print(next(foo))
    print(next(foo))
    
    
    def inc():
        yield from range(1000)
    foo = inc()
    print(next(foo))
    print(next(foo))
    print(next(foo))
    
    结果都为:
    0
    1
    2

    上面的两端代码其实是等效的。

    再比如下面这个例子,从一个可迭代对象中一个个拿元素。

    def counter(n): 
        for x in range(n):
            yield x
    
    def inc(n):
        yield from counter(n)
    
    foo = inc(10)
    print(next(foo))
    print(next(foo))
    print(next(foo))
    
    结果为:
    0
    1
    2
     
     
  • 相关阅读:
    IOS开发 网络发展史(NSURLProtocol)
    IOS开发 网络发展史(NSURLCach)
    IOS 开发 网络发展史(URLConnection)
    ios 网络开发(CFNetwork)
    MAC安装mysql
    conda 安装常用包
    从必应上拉取图片
    旋转数组中的二分查找
    东北大学 python模拟登录校园网(2019年6月启用新登录模式后)
    Ubuntu 安装Codeblocks
  • 原文地址:https://www.cnblogs.com/xpc51/p/11695736.html
Copyright © 2011-2022 走看看