• 函数进阶之生成器和迭代器


    前提:

    列表生成式

    给列表a里的大于5的每一个值加10

    a = [1, 2, 5, 6, 7, 8]
    a = [i + 10 if i > 5 else i for i in a]  # 可以循环任何可循环的东西,不过只能写到列表或元组里。
    print(a)  # [1, 2, 5, 16, 17, 18]

    复杂东西列表生成式写不出来,最多到三元运算了。

    正文:

    生成器是为了省内存,不一次释放,需要一个取一个。

    g = (i for i in range(5))
    print(g)  # <generator object <genexpr> at 0x101fba048>
    print(next(g))  # 0
    print(next(g))  # 1
    print(next(g))  # 2
    print(next(g))  # 3
    print(next(g))  # 4
    print(next(g))  # 到最后没有了,会报错

    生成器只能往前走,不能后退。

    我们创建了一个generator后,基本上永远不会调用next(),而是通过for循环来迭代它,并且不需要关心StopIteration的错误,for循环会把它自动消化。

    g2 = (i for i in range(5))
    
    for i in g2:
        print(i)

    generator非常强大。如果推算的算法比较复杂,用类似列表生成式的for循环无法实现的时候,还可以用函数来实现。

    生成器和range

    python2里range是直接创建列表。
    python3里的range是用生成器做的,是循环到了才创建。
    python2里有个xrange,其实就是等于python3里的range。python3只有range,等于是把python2里的那个range去掉了。

    用生成器实现斐波那契

    def fib(max):
        n, a, b = 10, 0, 1
        while n < max:
            print('before yield')yield n  # 把函数的执行过程冻结在这一步,并且把b的值,返回给外面的next()
            print(b)
            a, b = b, a + b
            n += 1
        return 'done'
    
    
    print(fib(15))  # <generator object fib at 0x101fba048> 里面有yield,函数名加括号,内部代码根本不执行,只是生成一个生成器对象。
    
    f = fib(15)  # return function into a generator
    
    next(f)  # before yield  # first time call next()
    n = next(f)  # 1 # before yield
    next(f)  # 1 # before yield
    next(f)  # 2 # before yield
    
    print(n)  # 11

    yield的作用是把函数里面的值返回出来到外部

    解析:第一次print出 before yield之后,遇到yield,程序终止,再次执行next(f),程序继续运行print出b的值,然后直到再次走到print('before yield')后,
    程序遇到yield n,程序终止,然后再次执行next(f),以此循环......

    生成器的创建方式

    1. "列表"生成式(),最多支持三元表达式。 例:g2 = (i for i in range(5))
    2. 函数

    用生成器实现range方法

    def range2(n):
        count = 0
        while count < n:
            print('count', count)
            yield count
            count += 1
    
    
    new_range = range2(10)
    r1 = next(new_range)
    print(r1)  # 0
    r2 = new_range.__next__()  # 和 next(new_range)是一样的
    print(r2)  # 1

    yield vs return

    return 返回并终止函数
    yield 返回数据,并冻结当前的执行过程

    next 唤醒冻结的函数执行过程,继续执行,直到遇到下一个yield

    函数有了yield之后
    1.函数名加()就变成了一个生成器
    2.return在生成器里,代表生成器的终止,直接报错

    生成器send方法

    def range2(n):
        count = 0
        while count < n:
            print('count', count)
            sign = yield count
            count += 1
            if sign == 'stop':
                print('----sign ', sign)
                break
    
    
    new_range = range2(10)
    n1 = next(new_range)  # count 0
    
    n2 = new_range.__next__()  # count 1
    
    n3 = new_range.send(None)  # count 2 这条语句等于next,因为next(或__next__)方法就是默认往生成器里面发送了一个None
    
    n4 = new_range.send('stop')  # 终止,程序报错,打印----sign  stop

    send的作用:
    1.唤醒并继续执行
    2. 发送一个信息到生成器的内部

    next()默认往生成器里发送一个None

    迭代器

    可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator。

    可以使用isinstance()判断一个对象是否是Iterable对象

    from collections import Iterable
    
    print(isinstance(123, Iterable))  # False
    print(isinstance('abc', Iterable))  # True

    只有生成器是迭代器。

    from collections import Iterator
    
    li = [i for i in range(10)]
    print(isinstance(li, Iterator))  # False

    但是可以把列表、字符串等变成迭代器

    li = iter(li)
    
    print(li.__next__())  # 0
    print(li.__next__())  # 1

    Python的Iterator对象表示的是一个数据流,Iterator对象可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。
    可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,所以Iterator的计算是惰性的,
    只有在需要返回下一个数据时它才会计算。Iterator甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。

    需要注意的是:

      1.列表用len能知道长度,但是迭代器不能知道
      2.迭代器比生成器的范围要大一些,学了面向对象后不用生成器也能next。

    凡是可作用于for循环的对象都是Iterable类型;
    凡是可作用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列;
    集合数据类型如list、dict、str等是Iterable但不是Iterator,不过可以通过iter()函数获得一个Iterator对象。
    Python3的for循环本质上就是通过不断调用next()函数实现的

    生成器算得上是Python语言中最吸引人的特性之一,生成器其实是一种特殊的迭代器,不过这种迭代器更加优雅。
    它不需要再像上面的类一样写__iter__()和__next__()方法了,只需要一个yiled关键字。
    生成器一定是迭代器(反之不成立),因此任何生成器也是以一种懒加载的模式生成值。

    生成器 VS 迭代器 (补充)

    1. 迭代器(Iterator)

    这里的迭代可以指for循环,在Python中,对于像list,dict和文件等而言,都可以使用for循环,但是它们并不是迭代器,它们属于可迭代对象。
    1.1 什么可迭代对象
    最简单的解释:可以使用for...in...语句进行循环的对象,就是可迭代对象(Iterable),可以使用isinstance()方法进行判断。

    from collections import Iterable 
    type = isinstance('python', Iterable)
    print type

    1.2 什么是迭代器
    迭代器指的是可以使用next()方法来回调的对象,可以对可迭代对象使用iter()方法,将其转换为迭代器。

    temp = iter([1, 2, 3])
    print type(temp)
    print next(temp)

    此时temp就是一个迭代器。所以说,迭代器基于两个方法:

    • next:返回下一个项目
    • _iter_ 返回迭代器本身
      可理解为可被next()函数调用并不断返回下一个值的对象就是迭代器,在定义一个装饰器时将需要同时定义这两个方法。
      迭代器的优势
      在构建迭代器时,不是将所有的元素一次性的加载,而是等调用next方法时返回元素,所以不需要考虑内存的问题。
      迭代器应用场景
      那么,具体在什么场景下可以使用迭代器呢?
    • 数列的数据规模巨大
    • 数列有规律,但是不能使用列表推导式描述。

    1.3 文件对象

    f = open('myfile.txt')
    print(next(f))
    print(next(f))
    print(next(f))
    print(next(f))
    
    hello text file
    goodbyt text file
    hahahahah
    Traceback (most recent call last):
     File "E:/12homework/12homework.py", line 5, in <module>
    print(next(f))
    StopIteration

     文件对象的迭代器就是他自己。即文件对象既是迭代器,又是可迭代对象.

    2. 生成器

    生成器是一种高级迭代器,使得需要返回一系列元素的函数所需的代码更加的简单和高效(不像创建迭代器代码那般冗长)。
    2.1 生成器函数
    生成器函数和常规函数都采用def语句进行定义,但是基于yield指令返回一个值,可以暂停一个函数并返回中间结果。当需要一个将返回一个序列或在循环中执行的函数时,就可以使用生成器,因为当这些元素被传递到另一个函数中进行后续处理时,一次返回一个元素可以有效的提升整体性能。
    常见的应用场景是使用生成器的流数据缓冲区。

    2.2 生成器表达式
    生成式表达式是一种实现生成器的便捷方式,将列表推导式的中括号替换为圆括号。
    和列表推导式的区别:列表生成式可以直接创建一个表,但是生成器表达式是一种边循环边计算,使得列表的元素可以在循环过程中一个个的推算出来,不需要创建完整的列表,从而节省了大量的空间。


    g = (x * x for x in range(10))

    总结:生成器是一种高级迭代器。生成器的优点是延迟计算,一次返回一个结果,这样非常适用于大数据量的计算。但是,使用生成器必须要注意的一点是:生成器只能遍历一次。

  • 相关阅读:
    Java中XML的四种解析方式(二)
    Java中XML的四种解析方式(一)
    Java反射机制
    创建线程的三种方式(Thread、Runnable、Callable)
    GBK和UTF-8的区别
    selenium的其他方法
    selenium
    XPath
    mysqldump / MySQL 备份与恢复语句
    MySQL 增删改查语句/SELECT INSET INTO UPDATE
  • 原文地址:https://www.cnblogs.com/lshedward/p/9987666.html
Copyright © 2020-2023  润新知