• python基础--迭代器、生成器


    (1)迭代器

    可迭代对象和迭代器的解释如下:

    '''
    什么是对象?Python中一切皆对象,之前我们讲过的一个变量,一个列表,一个字符串,文件句柄,函数名等等都可称作一个对象,其实一个对象就是一个实例,就是一个实实在在的东西。那么什么叫迭代?其实我们在日常生活中经常遇到迭代这个词儿,更新迭代等等,迭代就是一个重复的过程,但是不能是单纯的重复(如果只是单纯的重复那么他与循环没有什么区别)每次重复都是基于上一次的结果而来。比如你爹生你,你生你爹,哦不对,你生你儿子,你儿子生你孙子等等,每一代都是不一样的;还有你使用过得app,微信,抖音等,隔一段时间就会基于上一次做一些更新,那么这就是迭代。可迭代对象从字面意思来说就是一个可以重复取值的实实在在的东西。
    '''
    
    '''
    1.可迭代对象:
    字面意思:对象?python中一切皆对象。一个实实在在存在的值,就是对象
    可迭代?:更新迭代。重复的,循环的一个过程,更新迭代每次都有新的内容。
    可以进行循环更新的一个实实在在的值。
    专业角度:可迭代对象?内部含有__iter__方法的对象,可迭代对象。
    目前学过的可迭代对象?str list tuple dict set range 文件句柄
    
    2.获取对象的方法
    dir()
    3.判断一个对象是否是可迭代的对象
    s1 = 'adasda'
    l1 = [1, 2, 3]
    print(dir(s1)) # 获取所有的方法。
    print(dir(l1))  # 获取列表的所有的方法。
    print('__iter__' in dir(s1))  # True
    print('__iter__' in dir(range(10)))  # True
    # 输出的结果:True表示是可迭代对象,False表示不是可迭代对象。
    
    4.小结
    字面意思:可以进行循环更新的一个实实在在的值。
    专业角度:内部含有__iter__方法的对象,表示是可迭代对象。
    判断一个对象是不是可迭代对象:__iter__方法是否在dir(对象)中。
    str list tuple dict set range
    优点:1.存储的数据直接能显示,比较直观。2.拥有的方法比较多,操作方便。
    缺点:1.占用内存。2.不能直接通过for循环,不能直接取值(索引,key除外)。但是在实际的时候我们列表和字符串等等可迭代对象可以通过for循环取值,是因为我们封装了__next__方法,变成了迭代器。那么就可以直接通过for循环取值。
    
    5.迭代器
    (1)迭代器的定义
        字面意思:更新迭代,器:工具。可更新迭代的工具
        专业角度:内部含有__iter__方法并且含有__next__方法的对象就是迭代器。
    (2)判断一个对象是否是是迭代器
        __iter__方法 and __next__方法是否在不在dir(对象)中
        我们目前学的迭代器只有文件句柄。
    
    6.可迭代对象如何转换为迭代器
    iter()将可迭代对象转换为迭代器---》iter([1,2,3])
    s1 = 'asdasdcsadfc'
    obj = iter(s1)  # s1.__iter__()
    print(obj) # <str_iterator object at 0x0000022ED5F3CF08>
    
    print(next(obj))
    # next(obj)
    print(obj.__next__())
    # obj.__next__()
    
    迭代器的取值
        s1 = 'sdwfe'
    obj = iter(s1) # 这里将可迭代对象转换为迭代器
    print(obj)  # <str_iterator object at 0x000001F7ACF4CA88>
    print(next(obj))  # s
    print(next(obj))  # d
    print(next(obj))  # w
    print(next(obj))  # f
    print(next(obj))  # e
    # print(next(obj))  # StopIteration
    
    # Traceback (most recent call last):
    #   File "D:/Program Files (x86)/DjangoProjects/basic/day11/04 迭代器.py", line 96, in <module>
    #     print(next(obj))
    # StopIteration
    # 迭代器利用next取值:一个next取对应的一个值,如果迭代器里面的值取完了,还要next,
    # 那么就报StopIteration的错误。
    
    l1 = [11, 22, 33, 44, 55, 66]
    obj = iter(l1)  # 对列表进行操作形成了迭代器
    print(next(obj))
    print(next(obj))
    print(next(obj))
    print(next(obj))
    print(next(obj))
    print(next(obj))
    输出的结果为:
    11
    22
    33
    44
    55
    66
    '''
    
    '''
    小结
    	(1)可迭代对象:
    	字面意思:可以进行循环更新的一个实实在在的值。
    	专业角度:内部含有__iter__方法的对象,表示是可迭代对象。
    	判断一个对象是不是可迭代对象:__iter__方法是否在dir(对象)中。
    	str list tuple dict set range
    	优点:1.存储的数据直接能显示,比较直观。2.拥有的方法比较多,操作方便。
    	缺点:1.占用内存。2.不能直接通过for循环,不能直接取值(索引,key除外)。但是在实际的时候	 我们列表和字符串等等可迭代对象可以通过for循环取值,是因为我们封装了__next__方法,变成了迭代器。那么就可以直接通过for循环取值。	
    	(2)迭代器:
        字面意思:更新迭代,器:工具。可更新迭代的工具
        专业角度:内部含有__iter__方法并且含有__next__方法的对象就是迭代器。
        优点:1.节省内存。迭代器在内存中相当于只占一个数据的空间:因为每次取值都上一条数据会在内存释放,加载当前的此条数据。2.惰性机制。next一次,取一个值,绝不过多取值。
        缺点:1.速度慢:用时间换空间。2.不走回头路,不直观。3.操作方法单一。
    可迭代对象与迭代器的对比
        (几百万个对象,8G内存是可以接受的,承受的)
        可迭代对象是一个操作方法比较多,比较直观的,存储数据相对少的一个数据集。
        当你侧重于对于数据可以灵活处理,并且内存空间足够,将数据集设置为可迭代对象是明确的选择
        当你的数据量大,大到足以撑爆你的内存或者你以节省内存为首选因素时,将数据集
        设置为一个迭代器是一个不错的选择。
    '''
    l1 = [1, 2, 3, 4, 5, 6]
    obj = iter(l1)
    # obj = l1.__iter__()
    
    for i in range(2):
        print(next(obj))
    
    for i in range(2):
        print(next(obj))
    # 输出的结果为:
    # 1
    # 2
    # 3
    # 4
    
    # while循环模拟for循环(这个是面试题,直接手写面试题)
    # 刚才我们提到了,for循环的循环对象一定要是可迭代对象,但是这不意味着可迭代对象就可以取值,因为for循环的内部机制是:将可迭代对象转换成迭代器,然后利用next进行取值,最后利用异常处理处理StopIteration抛出的异常。
    l1 = [1, 2, 3, 4, 5, 6]
    # 1 将可迭代对象转化成迭代器
    obj = iter(l1)
    # 2,利用while循环,next进行取值
    while 1:
        # 3,利用异常处理终止循环
        try:
            print(next(obj))
        except StopIteration:
            break
    

    我们今天比较深入的了解了可迭代对象与迭代器,接下来我们说一下这两者之间比较与应用:

    可迭代对象:是一个私有的方法比较多,操作灵活(比如列表,字典的增删改查,字符串的常用操作方法等),比较直观,但是占用内存,而且不能直接通过循环迭代取值的这么一个数据集。

    应用:当你侧重于对于数据可以灵活处理,并且内存空间足够,将数据集设置为可迭代对象是明确的选择。

    迭代器:是一个非常节省内存,可以记录取值位置,可以直接通过循环+next方法取值,但是不直观,操作方法比较单一的数据集。

    应用:当你的数据量过大,大到足以撑爆你的内存或者你以节省内存为首选因素时,将数据集设置为迭代器是一个不错的选择。(可参考为什么python把文件句柄设置成迭代器)。

    (2)生成器

    什么是生成器?这个概念比较模糊,各种文献都有不同的理解,但是核心基本相同。生成器的本质就是迭代器,在python社区中,大多数时候都把迭代器和生成器是做同一个概念。不是相同么?为什么还要创建生成器?生成器和迭代器也有不同,唯一的不同就是:迭代器都是Python给你提供的已经写好的工具或者通过数据转化得来的,(比如文件句柄,iter([1,2,3])。生成器是需要我们自己用python代码构建的工具。最大的区别也就如此了。

    '''
    生成器:python社区,生成器和迭代器看成是一种。
    区别:生成器是我们自己用python代码构建的数据和工具。
    迭代器都是提供的,或者转化得来的。迭代器都是Python给你提供的已经写好的工具或者通过数据转化得来的。
    
    获取生成器的三种方式:
    1.生成器函数(自己写的)
    2.生成器表达式(自己写的)
    3.python内部提供的一些内置函数,得到的值就是一个生成器
    
    获取迭代器的方式:
    1.python提供的,文件句柄
    2.通过可迭代对象转换的,-iter()方法
    3.python内部提供的一些内置函数,得到的值就是一个迭代器
    '''
    

    我们先来研究通过生成器函数构建生成器。

    1.yield的用法

    首先,我们先看一个很简单的函数:

    def func():
        print(11)
        return 22
    ret = func()
    print(ret)
    # 运行结果:
    # 11
    # 22
    

    将函数中的return换成yield,这样func就不是函数了,而是一个生成器函数

    def func():
        print(11)
        yield 22
    

    我们这样写没有任何的变化,这是为什么呢? 我们来看看函数名加括号获取到的是什么?

    def func():
        print(11)
        yield 22
    ret = func()
    print(ret)
    # 运行结果:
    <generator object func at 0x000001A575163888>
    

    运行的结果和最上面的不一样,为什么呢?? 由于函数中存在yield,那么这个函数就是一个生成器函数.

    我们在执行这个函数的时候.就不再是函数的执行了.而是获取这个生成器对象,那么生成器对象如何取值呢?

    之前我们说了,生成器的本质就是迭代器.迭代器如何取值,生成器就如何取值。所以我们可以直接执行next()来执行以下生成器

    def func():
         print("111")
         yield 222
    gener = func() # 这个时候函数不会执⾏. ⽽是获取到⽣成器
    ret = gener.__next__() # 这个时候函数才会执⾏
    print(ret)  # 并且yield会将func生产出来的数据 222 给了 ret。  
    # 结果:
    # 111
    # 222
    

    并且我的生成器函数中可以写多个yield。

    def func():
        print("111")
        yield 222
        print("333")
        yield 444
    gener = func()
    ret = gener.__next__()
    print(ret)
    ret2 = gener.__next__()
    print(ret2)
    ret3 = gener.__next__()
    # 最后⼀个yield执⾏完毕. 再次__next__()程序报错
    print(ret3)
    结果:
    111
    222
    333
    444
    
    def eat():
        for i in range(1, 10000):
            yield '包子' + str(i)
    e = eat()
    for i in range(200):
         print(next(e))
    
    for i in range(300):
         print(next(e))
    

    当程序运行完最后一个yield,那么后面继续运行next()程序会报错,一个yield对应一个next,next超过yield数量,就会报错,与迭代器一样。

    yield与return的区别:

    ​ return一般在函数中只设置一个,他的作用是终止函数,并且给函数的执行者返回值。

    ​ yield在生成器函数中可设置多个,他并不会终止函数,next会获取对应yield生成的元素。

    2.yield from

    在python3中提供一种可以直接把可迭代对象中的每一个数据作为生成器的结果进行返回

    # 对比yield 与 yield from 
    def func():
        lst = ['卫龙','老冰棍','北冰洋','牛羊配']
        yield lst
    g = func()
    print(g)
    print(next(g))  # 只是返回一个列表
    
    def func():
        lst = ['卫龙','老冰棍','北冰洋','牛羊配']
        yield from lst
    g = func()
    print(g)
    # 他会将这个可迭代对象(列表)的每个元素当成迭代器的每个结果进行返回。
    print(next(g))
    print(next(g))
    print(next(g))
    print(next(g))
    '''
    yield from ['卫龙','老冰棍','北冰洋','牛羊配'] 
    等同于:
        yield '卫龙'
        yield '老冰棍'
        yield '北冰洋'
        yield '牛羊配'
    '''
    
    '''
    输出的结果为:
    <generator object func at 0x000001A7383760C8>
    ['卫龙', '老冰棍', '北冰洋', '牛羊配']
    <generator object func at 0x000001A7383762C8>
    卫龙
    老冰棍
    北冰洋
    牛羊配
    '''
    

    有个小坑,yield from 是将列表中的每一个元素返回,所以 如果写两个yield from 并不会产生交替的效果

    def func():
        lst1 = ['卫龙','老冰棍','北冰洋','牛羊配']
        lst2 = ['馒头','花卷','豆包','大饼']
        yield from lst1
        yield from lst2
        
    g = func()
    for i in g:
        print(i)
    '''
    卫龙
    老冰棍
    北冰洋
    牛羊配
    馒头
    花卷
    豆包
    大饼
    '''
    

    列表推导式

    # 列表推导式
    # 用一行代码去构建一个比较复杂有规律的列表
    # l1 = []
    # for i in range(1,11):
    #     l1.append(i)
    # print(l1) # [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    
    # 列表推导式(循环模式)
    l1 = [i for i in range(1, 11)]
    print(l1)  # [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    
    # 列表推导式分为两类
    # 1.循环模式:[变量(加工后的变量)for 变量 in iterable]
    # 2.筛选模式:[变量(加工后的变量)for 变量 in iterable if条件]
    # 循环模式
    # l1 = [i for i in range(1,11)]
    # print(l1) # [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    
    将10以内所有整数的平方写入列表。
    l1 = [i*i for i in range(1,11)]
    print(l1)
     
    100以内所有的偶数写入列表.
    l1 = [i for i in range(2,101,2)]
    print(l1)
     
    从python1期到python100期写入列表lst
    lst = [f'python{i}' % i for i in range(1,19)]
    print(lst)
    
    将这个列表中大于3的元素留下来。
    l1 = [4, 3, 2, 6, 5, 5, 7, 8] 
    print([i for i in l1 if i > 3])
    
    三十以内可以被三整除的数。
    multiples = [i for i in range(30) if i % 3 is 0]
    print(multiples)
     
    过滤掉长度小于3的字符串列表,并将剩下的转换成大写字母
    l = ['wusir', 'laonanhai', 'aa', 'b', 'taibai']
    # print([i.upper() for i in l if len(i) > 3])
    找到嵌套列表中名字含有两个‘e’的所有名字(有难度)
    
    names = [['Tom', 'Billy', 'Jefferson', 'Andrew', 'Wesley', 'Steven', 'Joe'],
             ['Alice', 'Jill', 'Ana', 'Wendy', 'Jennifer', 'Sherry', 'Eva']]
    print([name for lst in names for name in lst if name.count('e') >= 2])  # 注意遍历顺序,这是实现的关键
    # 列表推导式基本上讲完了,当然今天会做一些有关列表推导式的题,让大家更加深入的了解。
    

    我们再来研究通过生成器表达式构建生成器。

    生成器表达式和列表推导式的语法上一模一样,只是把[]换成()就行了。比如将十以内所有数的平方放到一个生成器表达式中。

    gen = (i**2 for i in range(10))
    print(gen)
    # 结果: <generator object <genexpr> at 0x0000026046CAEBF8>
    

    生成器表达式也可以进行筛选

    # 获取1-100内能被3整除的数
    gen = (i for i in range(1,100) if i % 3 == 0)
    for num in gen:
        print(num)
    

    生成器表达式和列表推导式的区别:

    1. 列表推导式比较耗内存,所有数据一次性加载到内存。而.生成器表达式遵循迭代器协议,逐个产生元素。

    2. 得到的值不一样,列表推导式得到的是一个列表.生成器表达式获取的是一个生成器

    3. 列表推导式一目了然,生成器表达式只是一个内存地址。

    无论是生成器表达式,还是列表推导式,他只是Python给你提供了一个相对简单的构造方式,因为使用推导式非常简单,所以大多数都会为之着迷,这个一定要深重,推导式只能构建相对复杂的并且有规律的对象,对于没有什么规律,而且嵌套层数比较多(for循环超过三层)这样就不建议大家用推导式构建。

    生成器的惰性机制: 生成器只有在访问的时候才取值,说白了.你找他要才给你值.不找他要.他是不会执行的.

    字典推导式

    根据名字应该也能猜到,推到出来的是字典

    lst1 = ['jay','jj','meet']
    lst2 = ['周杰伦','林俊杰','郭宝元']
    dic = {lst1[i]:lst2[i] for i in range(len(lst1))}
    print(dic)
    

    集合推导式

    集合推导式可以帮我们直接生成一个集合,集合的特点;无序,不重复 所以集合推导式自带去重功能

    lst = [1,2,3,-1,-3,-7,9]
    s = {abs(i) for i in lst}
    print(s) # {1, 2, 3, 7, 9}
    

  • 相关阅读:
    WordPress网站绑定多个域名的方法
    htpasswd 命令使用
    在Windows下用OpenSSL生成证书步骤
    WCF中关于List和数据的转换问题
    NET2.0的配置文件
    C# Attribute
    c#自定义属性
    VS2005中读写配置文件(方法二)
    c#的反射
    Asp.NET 操作配置文件 Steven Pei 博客园
  • 原文地址:https://www.cnblogs.com/stu-zhouqian/p/13214757.html
Copyright © 2020-2023  润新知