zoukankan      html  css  js  c++  java
  • 可迭代对象、迭代器对象和生成器对象



    可迭代对象iterable

    字面意思:

    • 可迭代:更新迭代。迭代是一个重复的过程,每次重复是基于上一次的结果而继续的,每次都有新的内容。
    • 可迭代对象:可以进行循环更新的一个实实在在的值。

    专业角度:

    内部含有'__iter__'方法的对象。

    str.__iter__
    list.__iter__
    set.__iter__
    dict.__iter__
    tuple.__iter__
    

    优点:

    1、存储的数据能直接显示,比较直观。

    2、拥有的方法比较多,操作方便。

    缺点:

    1、占内存,有多少值就会占用多少内存。

    2、只能通过索引,key取值。

    for循环能进行取值是因为在底层做了转换,将可迭代对象转换成迭代器,然后取值。



    迭代器对象iterator

    字面角度:

    迭代器指的是迭代取值的工具(数据结构)。

    专业角度:

    内置有__next__方法和__iter__方法的对象。

    f = open('a.txt',mode'rt',encoding='utf-8')
    print('__iter__' in dir(f),'__next__' in dir(f))
    f.close()
    True True
    

    文件对象就是迭代器,这是为了避免当一个文件过大时,占用过多内存,所以Python做的优化。


    优点:

    1、提供一种不依赖索引的通用迭代取值方案。

    2、惰性计算,节省内存。每次在内存中仅存在一个值。

    缺点:

    • 1、速度慢,以时间换空间。
    • 2、取值麻烦。

    先将可迭代对象调用__iter__方法或iter()函数转换成迭代器,然后调用__next__方法或next()函数取值。

    li = [11,22,33]
    li_obj = li.__iter__()
    print(li_obj.__next__())
    print(li_obj.__next__())
    print(li_obj.__next__())
    print(li_obj.__next__())
    

    每调用一次__next__就会取一次值,当值都取完后,再调用__next__会抛StopIteration异常。

    StopIteration
    11
    22
    33
    
    • 3、没有__len__方法,无法预测值的长度,只能取到抛出异常才知道已经取完。
    s1 = {11,22,33}
    obj = iter(s1)
    print('__len__' in dir(obj))
    
    False
    
    • 4、一次性取值。
    li = [11,22,33]
    li_obj = iter(li)
    for i in li_obj:
        print(i)
    
    print('====分隔符====')
    for i in li_obj:
        print(i)
    

    在第一个for循环取完值后,第二次for循环已经取不出来值了。所以结果为:

    11
    22
    33
    ====分隔符====
    

    除非重新调用iter()转换成迭代器,就可以重新取值。

    li_obj2 = iter(li)
    for i in li_obj:
        print(i)
    
    11
    22
    33
    ====分隔符====
    11
    22
    33
    

    使用while循环模拟for循环对迭代器取值

    1、调用可迭代对象.__iter__方法,得到一个迭代器对象。

    2、迭代器调用next(),获取一个值。

    3、循环取值,直到抛出StopIteration异常,捕捉异常然后结束循环。

    s1 = {11,22,33}
    obj = iter(s1)
    while 1:
        try:
            print(next(obj))
        except StopIteration:  # 当捕捉到StopIteration异常时就break结束循环。
            break
    

    将可迭代对象转换为迭代器就可以实现不依赖索引或key也能取值的目的。


    可迭代对象转换为迭代器对象

    可迭代对象调用__iter__方法或iter()函数会转换成迭代器。每次转换得到的都是一个新的迭代器。

    li = [11,22,33]
    li_obj = iter(li)
    li_obj2 = iter(li)
    print(li_obj is li_obj2)  # 两次都得到一个新的迭代器。
    False
    

    两个迭代器都可以取值。

    for i in li_obj:
        print(i)
    
    for i in li_obj2:
        print(i)
        
    11
    22
    33
    11
    22
    33
    

    如果是迭代器对象调用__iter__方法或iter()函数,所得到的结果为迭代器对象本身。这么设计是为了方便for循环的工作机制,使迭代器对象无论调用__iter__方法多少次,得到的结果都是迭代器对象自身。

    li = [11,22,33]
    li_obj = iter(li)
    li_obj2 = li_obj.__iter__()
    li_obj3 = li_obj2.__iter__().__iter__()
    print(li_obj is li_obj2 is li_obj3)
    
    # 无论调用多少次,结果还是迭代器自身。
    True
    

    for 循环工作原理

    1、调用可迭代对象.__iter__方法,得到一个迭代器对象。

    2、调用next(迭代器),将返回值赋值给in前面的变量。

    3、循环取值,直到抛出StopIteration异常,for就会捕捉异常然后结束循环。

    s1 = {11,22,33}
    for i in s1:
        print(i)
        
    33
    11
    22
    

    由于迭代器对象无论调用__iter__方法多少次,得到的结果都是迭代器对象自身,所以for循环无论迭代的是可迭代对象还是一个迭代器对象,都可以直接调用该对象的__iter__方法,简化了for循环的设计思路。


    内置函数dir()

    获取一个对象的所有内置方法,以列表的形式返回。

    l = [1,2,3]
    print(dir(l))
    ['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']
    

    判断一个对象是否是可迭代对象。

    s = 'abc'
    print('__iter__' in dir(s))
    
    True
    

    生成器generator

    生成器的本质就是迭代器。

    区别:

    生成器是我们自己用Python代码构建的数据结构。迭代器是Python提供的,或者调用iter()转换得来的。

    def f():
        yield 10
    
    print('__iter__' in dir(f()) and '__next__' in dir(f()))
    True
    

    获取生成器的两种方式:

    1、yield构建生成器函数。

    2、生成器表达式。


    1、yield构建生成器函数。

    函数体内但凡出现yield关键字,调用函数将不会触发函数体代码的运行,而是会返回一个生成器对象。

    def func():
        print(111)
        yield 1
        print(222)
        yield 3
    ret = func()
    print(ret)
    print(next(ret))
    
    <generator object func at 0x0000000002338B30>
    111
    1
    

    特点:

    • 只要函数中有yield那么就是生成器函数。

    • 生成器函数加括号()并不会直接执行函数体代码,而是返回一个生成器对象。

      def func():
          print(111)
          yield 'aaa'
      obj = func()
      print(obj)
      
      # 并不会打印111
      <generator object func at 0x00000000025487B0>
      
    • 每调用一次生成器函数都会获得一个新的生成器。

      def func():
          print(111)
          yield 'aaa'
      obj = func()
      obj2 = func()  # 会得到一个新的生成器.
      print(obj is obj2)
      
      False
      
    • 函数中可以存在多个yield,并且yield不会终止函数。

    • 通过next取出yield返回的值,并会保留上次取值的位置。一个next对应一个yield,多了会报错。

      也就是说,yield可以暂停函数,然后我们可以拿到返回值进行处理后,用next方法再次触发函数代码的运行,协程方面有应用。


    return与yield的区别:

    相同点:在返回值反面用法一样。

    不同点:yield可以返回多次值,而return只能返回一次。


    用生成器函数手撸个range功能。

    def my_range(start,stop,step=1):
        while start < stop:
            yield start
            start += step
            
    for i in my_range(0,10):
        print(i)
    
    0
    2
    4
    6
    8
    

    用生成器函数手撸个无限取值的功能。

    def get_num():
        num = 0
        while 1:
            yield num
            num += 1
    
    obj = get_num()
    print(next(obj))
    print(next(obj))
    print(next(obj))
    
    
    结果为:
    0
    1
    2
    

    每调用一次生成器函数都会获得一个新的生成器,如果用下述写法,每次得到的是个新生成器,永远是取0:

    def get_num():
        num = 0
        while 1:
            yield num
            num += 1
    
    print(next(get_num()))
    print(next(get_num()))
    
    0
    0
    

    2、生成器表达式。

    首先讲列表推导式:能用一行代码构建一个比较复杂有规律的列表。

    格式:

    1、循环模式:

    l = [表达式 for 变量 in iterable]
    
    # 多层嵌套
    l = [表达式 for 变量 in iterable for 变量 in iterable ...]
    

    2、筛选模式:

    l = [表达式 for 变量 in iterable if 条件 for 变量 in iterable]
    

    超过三层循环才能构建成功的,不建议使用列表推导式。查找错误(debug模式)不行。

    # 常规创建
    li = []
    for i in range(1,10):
        if i > 3:
    		li.append(i)
        
    # [4, 5, 6, 7, 8, 9]
    
    # 列表推导式:
    li = [i for i in range(1,10) if i > 3]
    print(li)
    
    # [4, 5, 6, 7, 8, 9]
    

    生成器表达式与列表推导式的写法几乎一模一样。将列表推导式的方括号改为圆括号。也有筛选模式,循环模式,多层嵌套。

    实例:

    l = (i for i in range(10))
    print(next(l))
    print(next(l))
    print(next(l))
    
    0
    1
    2
    

    字典推导式

    l1 = ['jay','jj','meet']
    l2 = ['周杰伦','林俊杰','元宝']
    dic = {l1[i]:l2[i] for i in range(len(l1))}
    print(dic)
    
    # {'jay': '周杰伦', 'jj': '林俊杰', 'meet': '元宝'}
    
    items = [('a',1),('b',2),('c',3)]
    dic = {key:value for key,value in items if value > 1 }
    print(dic)
    
    # {'b': 2, 'c': 3}
    

    集合推导式:

    s1 = {i for i in range(1,4)}
    print(s1)
    {1, 2, 3}
    

    并没有元组推导式,因为有列表推导式就可以使用tuple()将列表转换成元组。

    li = tuple([i for i in range(1,10) if i > 3])
    print(li)
    
    (4, 5, 6, 7, 8, 9)
    

    表达式应用:

    计算一个文件内有多少个字符。

    with open('user.txt',mode='r',encoding='utf-8') as f:
    # 常规方法,读取每一行计算字符数,然后再累加
        d = 0
        for line in f:
            d += len(line)
        print(d)  
    
    # 可使用列表推导式简化,但文件行数若过多,会造成列表元素过多,占用大量内存空间
        d = sum([len(line) for line in f])
        print(d)
    
    # 使用生成器表达式,每次只会占用一块内存空间
        d = sum((len(line) for line in f))
        print(d)
    
    
    # 函数的括号和生成器表达式的括号可以合并。
        d = sum(len(line) for line in f)
        print(d)
    
  • 相关阅读:
    using 资源清理
    Http Module 介绍[转]
    一个类似CSDN的frameset框架
    vs.net2003的一个老问题“你试图打开的项目是Web项目,请指定URL路径”解决办法
    Oracle和SQL Server实现跨库查询
    Http 请求处理流程[转]
    Http Handler 介绍[转]
    asp.net后台控制div style
    sharepoint站点Feature的定制与开发
    为列表类型绑定Event Receiver
  • 原文地址:https://www.cnblogs.com/ChiRou/p/13409406.html
Copyright © 2011-2022 走看看