zoukankan      html  css  js  c++  java
  • Python学习日记(十) 生成器和迭代器

    使用dir()我们可以知道这个数据类型的内置函数有什么方法:

    print(dir(int))  
    print(dir(bool))
    print(dir([]))
    print(dir({}))
    print(dir(set))

    1.迭代器

    iterable:可迭代的

    迭代就是将数据能够一个一个按顺序取出来

    s = 'abc'
    print('__iter__' in dir(s))      #True
    li = [1,2]
    print('__iter__' in dir(li))     #True
    b = False
    print('__iter__' in dir(b))      #False
    i = 123
    print('__iter__' in dir(i))      #False
    dic = {}
    print('__iter__' in dir(dic))    #True
    set1 = set()
    print('__iter__' in dir(set1))   #True

    上面数据类型返回为真说明它是可以迭代的,反之是不可迭代的

    可迭代协议:

    就是内部要有一个__iter__()来满足要求

    当一个具有可迭代的数据执行__iter__()它将返回一个迭代器的内存地址

    print('abc'.__iter__()) #<str_iterator object at 0x005401F0>

    这里的iterator的意思是迭代器

    迭代器协议:

    现在有一个列表我们来看看它本身和在执行了__iter__()之后的方法有什么不同:

    li = [1,2,3,'a']
    print(dir(li))  #['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__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']
    print(dir(li.__iter__()))   #['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__length_hint__', '__lt__', '__ne__', '__new__', '__next__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setstate__', '__sizeof__', '__str__', '__subclasshook__']
    '''求两者的差集'''
    print(set(dir(li.__iter__())) - set(dir(li)))   #{'__length_hint__', '__setstate__', '__next__'}
    __length_hint__()的作用是求元素的长度
    __setstate__()的作用是指定索引值从哪里开始迭代
    __next__()的作用可以让值一个一个的取出

    在之前用到的for循环我们就是用__next__()这种方法进行取值,现在我们可以模拟for来写一个函数:
    li = [1,2,3,'a']
    def getItem(li):
        iterator = li.__iter__()
        while True:
             print(iterator.__next__())
    getItem(li)
    # 1
    # 2
    # 3
    # a
    #StopIteration 如果找不到元素就会报错

    如何处理掉这个异常?

    li = [1,2,3,'a']
    def getItem(li):
        iterator = li.__iter__()
        while True:
            try:
                print(iterator.__next__())
            except StopIteration:
                break
    getItem(li)
    # 1
    # 2
    # 3
    # a

    迭代器遵循迭代器协议:必须要有__iter__()和__next__()

    迭代器的好处:

      a.从容器类型中一个一个的取值,会把所有的值都取到

      b.节省内存的空间

         迭代器并不会在内存中占用一大块内存,而是随着循环每次生成一个,每一次使用__next__()来给值

    range():

    print(range(10000000))  #range(0, 10000000)

    实际上range()在调用的时候并没有真正生成这么多的值,如果真的生成的话那么内存可能会溢出

    print('__iter__' in dir(range(10)))     #True   
    print('__next__' in dir(range(10)))     #False
    from collections import Iterable
    from collections import Iterator
    print(isinstance(range(10),Iterable))    #True  是一个可迭代对象
    print(isinstance(range(10),Iterator))    #False 在执行后得到的结果并不是一个迭代器

    迭代器总结:

    1.可以被for循环的都是可迭代的

    2.可迭代的内部都有__iter__()方法

    3.只要是迭代器一定可以迭代

    4.可迭代的变量.__iter__()方法可以得到一个迭代器

    5.迭代器中的__next__()方法可以一个一个的获取值

    6.for循环实际上就是在使用迭代器

    2.生成器

    生成器函数

    本质上就是我们自己写的函数,只要含有yield关键字的函数就是生成器函数,yield不能和return共用且需要写在函数内部

    def generator():        #生成器函数
        print('a')
        yield 5
    ret = generator()       #ret是一个生成器
    print(ret)              #<generator object generator at 0x00503F00>

    生成器函数每次执行的时候会得到一个生成器作为返回值

    如果要返回函数值:

    def generator():        #生成器函数
        print('a')
        yield 5
        print('b')
        yield 4
    g = generator()        #g是一个生成器
    print(g)               #<generator object generator at 0x00503F00>
    ret = g.__next__()
    print(ret)              #a
                            #5
    print('---------')
    ret = g.__next__()
    print(ret)              #b
                            #4

    执行顺序:

    使用for循环遍历生成器:

    def generator():        #生成器函数
        print('a')
        yield 5
        print('b')
        yield 4
        print('c')
        yield 6
    g = generator()       #g是一个生成器
    for i in g:
        print(i)    
    # a
    # 5
    # b
    # 4
    # c
    # 6

    send():

    send()和__next__()都有获取下一个值的功能,但send()在获取下一个值的时候可以给上一个yield的位置传递一个数据,让用户进行接收

    send()在使用时需注意在第一次使用生成器时需用__next__()获取下一个值并且最后一个yield不能接受外部的值

    def generator():         #生成器函数
        print('a')           #1
        content = yield 1    #2
        print(content,'= 1') #3
        print('b')           #4
        content2 = yield 2   #5
        print(content2,'= 2')#6
        yield 3              #7
    
    g = generator()          #生成器g
    ret = g.__next__()       #执行1和2的右边语句
    print(ret)               #返回yield的值1
    ret = g.send('a')        #传递给yield一个值'a'让content接收,然后执行3,4语句
    print(ret)               #返回yield的值2
    ret = g.send('b')        #传递给yield一个值'b'让content2接收,然后执行6语句 yield再返回一个值3
    print(ret)
    # a
    # 1
    # a = 1
    # b
    # 2
    # b = 2
    # 3

    移动平均值例子:

    def average():          #定义一个平均值的生成器函数
        sum = 0
        count = 0
        avg = 0
        while True:
            num = yield avg
            sum += num
            count += 1
            avg = sum/count
    
    avg_g = average()       #取得生成器
    avg_g.__next__()
    avg1 = avg_g.send(int(input()))     #10
    avg1 = avg_g.send(int(input()))     #20
    print(avg1)                         #15

    预激装饰器的生成器

    def wrapper(func):
        def inner(*args,**kwargs):
            g = func(*args,**kwargs)
            g.__next__()
            return g
        return inner
    @wrapper                #wrapper = wrapper(func)
    def avgerage():
        sum = 0
        count = 0
        avg = 0
        while True:
            num = yield avg
            sum += num
            count += 1
            avg = sum/count
    avg_g = avgerage()
    print(avg_g.send(10))
    print(avg_g.send(20))
    print(avg_g.send(30))

    yiled form

    如何将字符串一个一个的去返回?

    一般方法:

    def generator():
        a = 'abcde'
        b = 'efdgh'
        for i in a:
            yield i
        for i in b:
            yield i
    g = generator()
    for i in g:
        print(i)

    用yiled form:

    def generator():
        a = 'abcde'
        b = 'efdgh'
        yield from a
        yield from b
    g = generator()
    for i in g:
        print(i)
    # a
    # b
    # c
    # d
    # e
    # e
    # f
    # d
    # g
    # h

    监听文件例子:

    def tail(filename):
        f = open(filename,encoding='utf-8')
        while True:
            line = f.readline()
            if line.strip():
                 yield line.strip()
    g = tail('tail')
    for i in g:
        if 'python' in i:
            print('******',i)   
    # ****** python
    # ****** asd python

    3.推导式

    优点:基本不消耗内存

    语法:以列表推导式为例

       (1)遍历元素进行挨个处理:[每一个元素或者是和元素相关的操作 for 元素 in 可迭代数据类型]  

       (2)筛选功能:[满足条件的元素的相关操作 for 元素 in 可迭代数据类型 if 元素相关的条件]

    1.列表推导式

    li = [i*i for i in range(5)]
    print(li)   #[0, 1, 4, 9, 16]

    找到50以内能被5整除的数:

    li = [i for i in range(50) if i % 5 == 0]
    print(li)   #[0, 5, 10, 15, 20, 25, 30, 35, 40, 45]

    找到两个列表中都含有一个'x'的元素

    names = [['xasd','12sa','xa'],['564','566','x233']]
    li = [name for lst in names for name in lst if name.count('x') == 1]
    print(li)   #['xasd', 'xa', 'x233']

    2.生成器推导式

    g = (i*2 for i in [1,5,6,7])
    print(g)    #<generator object <genexpr> at 0x006E4F00>
    for i in g:
        print(i)
    # 2
    # 10
    # 12
    # 14

    3.字典推导式

    将字典的键值互换

    dict = {'key1' : 'value1','key2' : 'value2','key3' : 'value3'}
    print(dict)         #{'key3': 'value3', 'key2': 'value2', 'key1': 'value1'}
    new_dict = {dict[i] : i for i in dict}
    print(new_dict)     #{'value2': 'key2', 'value1': 'key1', 'value3': 'key3'} 

    合并大小写对应的value值,再将键都统一成小写

    dict = {'a' : 1,'b' : 2,'A' : 3,'c' : 4}
    new_dict = {k.lower() : dict.get(k.lower(),0) + dict.get(k.upper(),0) for k in dict}    #如果没有找到这个值就返回0
    print(new_dict) #{'c': 4, 'b': 2, 'a': 4}

    4.集合推导式

    具有去重复的功能

    set = {x**2 for x in range(-5,6)}
    print(set)  #{0, 1, 4, 9, 16, 25}

    4.相关面试题

    例一:

    def generator():
        for i in range(4):
            yield i
    
    g = generator()
    g1 = (i for i in g)
    g2 = (i for i in g1)
    print(list(g1))         #[0, 1, 2, 3]       
    print(list(g2))         #[]       因为g2是往g1去拿值,但是g1已经从g用for循环遍历完了值,所以g2是一个空的列表

    例二:

    def add(n,i):
        return n+i
    def test():
        for i in range(4):  #0 1 2 3
            yield i
    g = test()
    for n in [1,10]:
        g = (add(n,i) for i in g)
    '''
    n = 1
    g = (add(n,i) for i in g)   这里的代码因为跑过了 就不执行
    n = 10
    g = (add(n,i) for i in (add(n,i) for i in test()))
    所以
    g = (add(10,i) for i in (add(10,i) for i in [0,1,2,3]))
    g = (add(10,i) for i in (10,11,12,13)
    g = (20,21,22,23)
    '''
    print(list(g))           #[20, 21, 22, 23]     当没有调用的时候上面的代码都不会执行
  • 相关阅读:
    HDU 1272 小希的迷宫 (并查集)
    HDU 5723 Abandoned country (最小生成树 + dfs)
    HDU 5744 Keep On Movin (贪心)
    探索Redis设计与实现2:Redis内部数据结构详解——dict
    探索Redis设计与实现1:Redis 的基础数据结构概览
    重新学习Mysql数据13:Mysql主从复制,读写分离,分表分库策略与实践
    重新学习MySQL数据库12:从实践sql语句优化开始
    重新学习MySQL数据库11:以Java的视角来聊聊SQL注入
    重新学习MySQL数据库10:MySQL里的那些日志们
    重新学习MySQL数据库9:Innodb中的事务隔离级别和锁的关系
  • 原文地址:https://www.cnblogs.com/Fantac/p/11343409.html
Copyright © 2011-2022 走看看