Python开发第五篇迭代器生成器面向过程编程

Posted 山大王

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python开发第五篇迭代器生成器面向过程编程相关的知识,希望对你有一定的参考价值。

阅读目录

一.迭代器

1. 迭代的概念

#迭代器即迭代的工具(自定义的函数),那什么是迭代呢?
#迭代:指一个重复的过程,每次重复都可以称之为一次迭代,并且每一次重复的结果是下一个迭代的初始值(例如:罚写作业100遍)

while True: #只是单纯地重复,因而不是迭代 print(===>) l=[1,2,3] count=0 while count < len(l): #迭代 print(l[count]) count+=1

2.为何要有迭代器? 什么是可迭代对象? 什么是迭代器对象?

技术分享图片
#1、为何要有迭代器?
对于序列类型:字符串、列表、元组,我们可以使用索引的方式迭代取出其包含的元素。但对于字典、集合、文件等类型是没有索引的,若还想取出其内部包含的元素,则必须找出一种不依赖于索引的迭代方式,这就是迭代器

#2、什么是可迭代对象?
可迭代对象指的是内置有__iter__方法的对象,即obj.__iter__,如下
hello.__iter__
(1,2,3).__iter__
[1,2,3].__iter__
{a:1}.__iter__
{a,b}.__iter__
open(a.txt).__iter__

#3、什么是迭代器对象?
可迭代对象执行obj.__iter__()得到的结果就是迭代器对象
而迭代器对象指的是即内置有__iter__又内置有__next__方法的对象

文件类型是迭代器对象
open(a.txt).__iter__()
open(a.txt).__next__()


#4、注意:
迭代器对象一定是可迭代对象,而可迭代对象不一定是迭代器对象
为何要有迭代器?什么是可迭代对象?什么是迭代器对象?

 3.迭代器对象的使用

技术分享图片
dic={a:1,b:2,c:3}
iter_dic=dic.__iter__() #得到迭代器对象,迭代器对象即有__iter__又有__next__,但是:迭代器.__iter__()得到的仍然是迭代器本身
iter_dic.__iter__() is iter_dic #True

print(iter_dic.__next__()) #等同于next(iter_dic)
print(iter_dic.__next__()) #等同于next(iter_dic)
print(iter_dic.__next__()) #等同于next(iter_dic)
# print(iter_dic.__next__()) #抛出异常StopIteration,或者说结束标志

#有了迭代器,我们就可以不依赖索引迭代取值了
iter_dic=dic.__iter__()
while 1:
    try:
        k=next(iter_dic)
        print(dic[k])
    except StopIteration:
        break
        
#这么写太丑陋了,需要我们自己捕捉异常,控制next,python这么牛逼,能不能帮我解决呢?能,请看for循环
迭代器对象的使用

4. for循环原理

#基于for循环,我们可以完全不再依赖索引去取值了
dic={‘a‘:1,‘b‘:2,‘c‘:3}
for k in dic:
    print(dic[k])

#for循环的工作原理
#1:执行in后对象的dic.__iter__()方法,得到一个迭代器对象iter_dic
#2: 执行next(iter_dic),将得到的值赋值给k,然后执行循环体代码
#3: 重复过程2,直到捕捉到异常StopIteration,结束循环

5. 迭代器的优缺点

#优点:
  - 提供一种统一的、不依赖于索引的迭代方式
  - 惰性计算,节省内存
#缺点:
  - 无法获取长度(只有在next完毕才知道到底有几个值)
  - 一次性的,只能往后走,不能往前退

二. 生成器

  1. 什么是生成器

   生成器:常规函数定义,但是,使用yield语句而不是return语句返回结果。yield语句一次返回一个结果,在每个结果中间,挂起函数的状态,以便下次重它离开的地方继续执行

   生成器表达式:类似于列表推导,但是,生成器返回按需产生结果的一个对象,而不是一次构建一个结果列表

#只要函数内部包含有yield关键字,那么函数名()的到的结果就是生成器,并且不会执行函数内部代码
def func():
    print(====>first)
    yield 1
    print(====>second)
    yield 2
    print(====>third)
    yield 3
    print(====>end)
g=func()
print(g) #<generator object func at 0x0000000002184360>

  2.生成器就是迭代器

g.__iter__
g.__next__
#所以生成器就是迭代器,因此可以这么取值
res=next(g)
print(res)

  3.生成器Generator总结:

    本质:迭代器(所以自带了__iter__方法和__next__方法,不需要我们去实现)

    特点:惰性运算,开发者自定义

  4.生成器函数

    一个包含yield关键字的函数就是一个生成器函数。yield可以为我们从函数中返回值,但是yield又不同于return,return的执行意味着程序的结束,调用生成器函数不会得到返回的具体的值,而是得到一个可迭代的对象。每一次获取这个可迭代对象的值,就能推动函数的执行,获取新的返回值。直到函数执行结束。

import time
def generator_fun1():
    a = 1
    print(现在定义了a变量)
    yield a
    b = 2
    print(现在又定义了b变量)
    yield b

g1 = generator_fun1()
print(g1 : ,g1)       #打印g1可以发现g1就是一个生成器
print(-*20)   #我是华丽的分割线
print(next(g1))
time.sleep(1)   #sleep一秒看清执行过程
print(next(g1))

  5.生成器有什么好处呢?

    1.延迟计算,一次返回一个结果。也就是说,它不会一次生成所有的结果,这对于大数据量处理,将会非常有用。

技术分享图片
# 假如我想让工厂给学生做校服,生产2000000件衣服,我和工厂一说,工厂应该是先答应下来,然后再去生产,我可以一件一件的要,也可以根据学生一批一批的找工厂拿。
# 而不能是一说要生产2000000件衣服,工厂就先去做生产2000000件衣服,等回来做好了,学生都毕业了。。。

def produce():
    """生产衣服"""
    for i in range(2000000):
        yield "生产了第%s件衣服"%i

product_g = produce()
print(product_g.__next__()) #要一件衣服
print(product_g.__next__()) #再要一件衣服
print(product_g.__next__()) #再要一件衣服
num = 0
for i in product_g:         #要一批衣服,比如5件
    print(i)
    num +=1
    if num == 5:
        break

#到这里我们找工厂拿了8件衣服,我一共让生产函数(也就是produce生成器函数)生产2000000件衣服。
#剩下的还有很多衣服,我们可以一直拿,也可以放着等想拿的时候再拿
示例

  6.练习

#1、自定义函数模拟range(1,7,2)

#2、模拟管道,实现时时获取文件中最新内容
技术分享图片
# 1、自定义函数模拟range(1,7,2)
def myRange(start,stop,step=1):
    while start < stop:
        yield start
        start += step

obj = myRange(1,7,2)
print(next(obj))
print(next(obj))
print(next(obj))
print(next(obj)) #StopIteration

#2、模拟管道,实现时时获取文件中最新内容
import time
def tail(filename):
    with open(filename,r,encoding=utf-8)as f:
        f.seek(0,2) #从文件末尾开始读取
        while True:
            line = f.readline()
            if not line:
                time.sleep(0.5)
                continue
            yield line

obj = tail(a.txt)
for i in obj:
    print(i)
代码示例

    7.协程函数

  什么是协程:

  协程是一个无优先级的子程序调度组件,允许子程序在特点的地方挂起恢复。(类似于看电影时的暂停播放)

  线程包含于进程,协程包含于线程。只要内存足够,一个线程中可以有任意多个协程,但某一时刻只能有一个协程在运行,多个协程分享该线程分配到的计算机资源。

#yield关键字的另外一种使用形式:表达式形式的yield
def eater(name):
    print(%s 准备开始吃饭啦 % name)
    food_list=[]
    while True:
        food = yield food_list
        print(%s 吃了 %s % (name, food))
        food_list.append(food)

e=eater(钢蛋)
print(e.send(None))  #初始化,对于表达式形式的yield,在使用时,第一次必须传None,e.send(None)等同于next(e)
print(e.send(包子))  
print(e.send(韭菜馅包子))
print(e.send(大蒜包子))
e.close() #关闭
print(e.send(大麻花))
#send 获取下一个值的效果和next基本一致 #只是在获取下一个值的时候,给上一yield的位置传递一个数据 #使用send的注意事项 # 第一次使用生成器的时候 是用send(None)或者 next进行初始化

 8.练习:

  1、编写装饰器,实现初始化协程函数的功能

技术分享图片
def init(func):
    def inner(*args,**kwargs):
        res = func(*args,**kwargs)
        next(res) #在装饰器中执行初始化方法
        return res
    return  inner

@init
def eater(name):
    print(%s 准备开始吃饭啦 % name)
    food_list=[]
    while True:
        food = yield food_list
        print(%s 吃了 %s % (name, food))
        food_list.append(food)

e=eater(钢蛋)
# e.send(None)
print(e.send(包子))
print(e.send(韭菜馅包子))
print(e.send(大蒜包子))
装饰器实现初始化协程方法

 9. yield 关键字 总结

1、把函数做成迭代器
2、对比return,可以返回多次值,可以挂起/保存函数的运行状态

三. 列表推导式、生成器表达式

  1.列表推导式

#1、示例
egg_list=[]
for i in range(10):
    egg_list.append(鸡蛋%s %i)

print(egg_list)

#列表推导式1
egg_list = [臭鸡蛋%s %i for i in range(0,10) ]
print(egg_list)

#列表推导式2
egg_list = [臭鸡蛋%s %i for i in range(0,10) if i>6]
print(egg_list)

#2、语法
[expression for item1 in iterable1 if condition1
for item2 in iterable2 if condition2
...
for itemN in iterableN if conditionN
]
类似于
res=[]
for item1 in iterable1:
    if condition1:
        for item2 in iterable2:
            if condition2
                ...
                for itemN in iterableN:
                    if conditionN:
                        res.append(expression)

#3、优点:方便,改变了编程习惯,可称之为声明式编程

  2.生成器表达式

#生成器表达式
#1、把列表推导式的[]换成()就是生成器表达式

#2、示例:生一筐鸡蛋变成给你一只老母鸡,用的时候就下蛋,这也是生成器的特性
chicken=(鸡蛋%s %i for i in range(5))

print(chicken) # generator object <genexpr> at 0x10143f200>

print(next(chicken)) #‘鸡蛋0‘

print(list(chicken)) #因chicken可迭代,因而可以转成列表 [‘鸡蛋1‘, ‘鸡蛋2‘, ‘鸡蛋3‘, ‘鸡蛋4‘,]


#3、优点:省内存,一次只产生一个值在内存中

  总结:

    1.把列表解析的[]换成()得到的就是生成器表达式

    2.列表解析与生成器表达式都是一种便利的编程方式,只不过生成器表达式更节省内存

 4.声明式编程练习题

1、将names=[egon,alex_sb,wupeiqi,yuanhao]中的名字全部变大写

2、将names=[egon,alex_sb,wupeiqi,yuanhao]中以sb结尾的名字过滤掉,然后保存剩下的名字长度

3、求文件a.txt中最长的行的长度(长度按字符个数算,需要使用max函数)

4、求文件a.txt中总共包含的字符个数?思考为何在第一次之后的n次sum求和得到的结果为0?(需要使用sum函数)

5、思考题
with open(a.txt) as f:
    g=(len(line) for line in f)
print(sum(g)) #为何报错?
技术分享图片
# 1、将names=[‘egon‘,‘alex_sb‘,‘wupeiqi‘,‘yuanhao‘]中的名字全部变大写
names=[egon,alex_sb,wupeiqi,yuanhao]
names = [name.upper() for name in names]
print([name.upper() for name in names])

# 2、将names=[‘egon‘,‘alex_sb‘,‘wupeiqi‘,‘yuanhao‘]中以sb结尾的名字过滤掉,然后保存剩下的名字长度
names=[egon,alex_sb,wupeiqi,yuanhao]
names = [len(name) for name in names if not name.endswith("sb") ]
print(names)
# 3、求文件a.txt中最长的行的长度(长度按字符个数算,需要使用max函数)
with open(a.txt,r,encoding=utf-8)as f:
    print(max(len(i) for i in f)) #最后有一个换行符

# 4、求文件a.txt中总共包含的字符个数?思考为何在第一次之后的n次sum求和得到的结果为0?(需要使用sum函数)
with open(a.txt,r,encoding=utf-8) as f:
    print(sum(len(i) for i in f))
    print(sum(len(i) for i in f))  #原因读取文件的指针已经到文件的末尾了
# 5、思考题
 with open(a.txt) as f:
     g=(len(line) for line in f)
 print(sum(g)) #为何报错?
#答: with open 在执行完文件操作后,会自动关闭掉此文件,所以再使用文件内容时会报错
代码示例

 




以上是关于Python开发第五篇迭代器生成器面向过程编程的主要内容,如果未能解决你的问题,请参考以下文章

第五篇Python之迭代器与生成器

Python成长之路第五篇:Python基础之迭代器和生成器

python开发 第五篇面向对象

python全栈开发-Day11 迭代器生成器面向过程编程

Python基础

python--迭代器生成器面向过程编程