python全栈学习总结五:迭代器和生成器

Posted qilvzhuiche

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了python全栈学习总结五:迭代器和生成器相关的知识,希望对你有一定的参考价值。

一 迭代器

1 什么是迭代器协议

  迭代器协议:对象必须提供一个next方法,执行该方法要么返回迭代中的下一项,要么引起一个Stoplteration异常,以终止迭代(只能往后走不能往前退)

  可迭代对象:实现了迭代器协议的对象(如何实现:对象内部定义一个__iter__()方法)

  协议是一种约定,可迭代对象实现了迭代器协议,python的内部工具(如for循环,sum,min,max函数等)使用迭代器协议访问对象。

2 自定义迭代器

class MyIterator:
    #自定义迭代器类
    def __init__(self,x = 2,xmax = 63):
        self.__mul,self.__x = x,x
        self.__xmax = xmax

    def __iter__(self): #定义迭代器协议方法,返回类本身
        return  self

    def __next__(self):#定义迭代器协议方法
        if self.__x and self.__x !=1:
            self.__mul *= self.__x
            if self.__mul <= self.__xmax:
                return  self.__mul
            else :
                raise StopIteration
        else :
            raise StopIteration

if __name__ == __main__:
    myiter = MyIterator(3,150) #实例化迭代器MyIterator
    for i in myiter:
        print("迭代的数据元素为:",i)

3 可迭代对象

  字符串,列表,元组,字典,集合,文件(str,list,tuple,dict,set,file)均为可迭代对象,通过for循环调用对象内部的__iter__方法,变成可迭代对象

  使用的迭代器协议函数

  x = ‘hell‘

  iter_x = x.__iter__()  #转化为可迭代对象,遵循迭代协议

  iter_x.__next__()  #转换后,可以使用__next__()方法来进行迭代

  next(iter_x)#也可以使用next函数进行迭代

  for循环的实质是在调用next方法或函数

 

  另外两个迭代函数
  iter(iterable)  参数iterable为可迭代类型

  iter(callable,sentine) 参数callable是可调用类型,一般为函数,第二个参数为哨兵,当第一个参数(函数)调用返回值等于第二个参数的值时,迭代或遍历停止。

class Counter: #定义用于计数的类
    def __init__(self,x=0): #定义构造函数,初始化实例属性
        self.x = x
counter = Counter()  #初始化实例类Counter
def used_iter():#定义用于iter()函数的函数
    counter.x +=2 #修改计数类中的实例属性的值
    return counter.x

for i in iter(used_iter,8):  #迭代iter()函数产生的迭代器
    print("本次遍历的数值:",i)

4 for和while操作可迭代对象,输出相同

x = hello
for i in x:
    print(i)

x = hello
x_iter = x.__iter__()
while True:
    try:
        print(x_iter.__next__())
    except StopIteration:
        print("迭代循环完成!")
        break

二 生成器

  生成器:一种数据类型,自动实现了迭代器协议(其他的数据类型需要调用自己内置的__iter__方法),所以生成器就是可迭代对象

1 三元表达式与列表解析

  三元表达式:res = ‘good‘ if num == True else ‘bad‘    #当num为True时,res的结果为‘good’,否则res的结果为‘bad‘  

  列表解析:l = [i for i in range(10)]  #生成列表l = [0,1,2,3,4,5,6,7,8,9]

        l1 = [i for i in range(10) if i>5]   #三元表达式与列表解析相结合。

2 生成器表达式创建

  (1)生成器表达式:num = (i for i in range(10) if i > 5)  #注意列表解析是用的[]而生成器表达式是()

#列表解析
ret_list = []
for i in range(10):
    ret_list.append(" 包子%s"%i)
print(ret_list)
ret_list = ["包子%s"%i for i in range(10)]
print(ret_list)
ret_list = ("包子%s"%i for i in range(10))
print(ret_list)
print(ret_list.__next__())
print(ret_list.__next__())
print(ret_list.__next__())

技术分享图片

注意观察列表解析和生成器创建的区别:列表解析一次生成列表,而生成器只是创建一个生成器对象,需要调用next方法才能访问。列表解析数据很多时,需要占用大量的内存。生成器的好处显而易见,用的时候才生成。

#生成器创建+三元判断
ret_list = ("包子%s"%i for i in range(10) if i > 5)
print(ret_list)
print(ret_list.__next__())
print(ret_list.__next__())
print(ret_list.__next__())

(2)生成器对象是通过yield关键字定义的函数对象,因此,生成器也是一个函数,生成器用于生成一个值的序列,以便在迭代中使用。

def myYiled(n): #定义一个生成器(函数)
    while n>0:
        print("开始生成...:")
        yield  n #yield语句,用于返回给调用者其后表达式的值
        print("完成一次...")
        n -= 1
if __name__ == __main__:
    for i in myYiled(4):
        print("遍历得到的值:",i)

    print(**10)
    my_yield = myYiled(3)
    print(已经实例化生成器对象!)
    print(my_yield.__next__())
    print(第二次调用__next__()方法)
    print(my_yield.__next__())
    print("第三次开始调用!")
    print(next(my_yield))

运行现象:

技术分享图片

yield语句是生成器中的关键语句,生成器在实例化时并不会立即执行,而是等待调用其__next__()方法的时候才开始执行,并且当程序运行完yield语句后就会保持(hold)住,即保持当前状态且停止运行,等待下一次遍历时,才恢复运行!如上图!!!!

使用生成器,可以生成一个值的序列用于迭代,并且这个值的序列不是一次生成的,而是使用一个,再生成一个,的确可以使程序节约大量内存!

yield语句不仅可以使函数成为生成器和返回值,还可以接收调用者传来的数值。但值得注意的是:第一次调用生成器不能传给生成器None以外的值,否则会引发错误!使用生成的send方法传值!

def myYield(n):
    while n >  0:
        rcv = yield  n #rcv用来接收调用者传来的值
        n -= 1
        if rcv is not None:
            n = rcv
if __name__ == __main__:

    my_yield = myYield(3)
    print(my_yield.__next__())
    print(next(my_yield))

    print(--*20)
    print("传给生成器一个值,重新初始化生成器!")
    print(my_yield.send(5))
    for i in my_yield:
        print(i)

    my_yield = myYield(3)
    #print(my_yield.send(4))  #xxxx第一次调用就传值错误!!!
    print(my_yield.send(None)) #但是可以传None
    print(next(my_yield))
    print(my_yield.send(6))

3 生成器特性:只能取一次

利用生成器来计算总人口,从文件中读取数据,分析出人口的总数量,并计算出所占比例

def get_population():
    with open("人口统计",r,encoding=utf-8) as f:
        for p in f:
            yield p
num_p = get_population()
# print(num_p.__next__())
# print(next(num_p))
all_p = sum(int(eval(p)[population]) for p in num_p)  #eval提取数据类型字典
print("总人口:%s"%all_p)
num_p = get_population()#上面已经取完,重新调用生成器
for num in num_p:
    num_dict = eval(num)
    # print("%s的比例:%s%%"%(10,8))
    print("%s的比例:%.2f%%"%(num_dict[name],int(num_dict[population])*100/all_p))

#人口统计文件
{"name":"济南","population":"100"} {"name":"青岛","population":"200"} {"name":"烟台","population":"300"} {"name":"潍坊","population":"400"} {"name":"菏泽","population":"500"} {"name":"临沂","population":"600"}

上面例程中,调用内置函数sum求和,使用生成器的方式计算,没有使用列表解析的方式,节省了大量内存,同理使用max,min等类似!一定要用生成器的方法!一个一个的计算!

4 生成器与协程(生产者与消费者)

运用send()方法来重置生成器的生成序列,也成为协程,是一种解决程序并发的方法!

def consumer(): #定义一个消费者模型(生成器协程)
    print("等待接收处理任务...")
    while True:
        data = (yield ) #模拟接收并处理任务
        print(收到任务!) #此处可以执行函数调用来完成相关任务

def producer():
    c = consumer()
    c.__next__()
    for i in range(5):
        print("发送一个任务...‘,‘任务%d"%i)
        c.send(任务%d%i)

if __name__ == __main__:
    producer()

 





以上是关于python全栈学习总结五:迭代器和生成器的主要内容,如果未能解决你的问题,请参考以下文章

Python全栈之路Day22

流畅python学习笔记:第十四章:迭代器和生成器

迭代器和生成器总结

Python学习笔记——迭代器和生成器

Python学习之迭代器和生成器

Python学习-迭代器和生成器