10.迭代器/生成器/协程函数/列表生成器

迭代器
为什么要用迭代器?
小结:
生成器
为什么要使用生成器,什么是生成器?
如何创建一个生成器
举个栗子:斐波拉契数列
用yield返回结果的执行流程
作业代码以及注释:
协程函数
面向过程编程
作业以及代码注解:
典型范例以及代码解析:
列表生成式
生成器表达式
作业和练习

迭代器

为什么要用迭代器?

提供了一种不依赖索引的取值方式,使一些不具有索引属性的对象也能遍历输出

相比列表,迭代器的惰性计算更加节约内存。

但是它无法有针对性地指定取某个值,并且只能向后取值。

>>> from collections import Iterable
>>> isinstance([],Iterable)
True

>>> from collections import Iterator
>>> isinstance((x for x in range(10)),Iterator)
True
>>> isinstance([],Iterator)
False
>>> isinstance({},Iterator)
False
>>> isinstance(‘abx‘,Iterator)
False

注意这两个单词:‘Iterable’和‘Iterator’

老师区分可迭代对象和迭代器的方法,就是对象是否内置.__iter__方法。而这个方法的运行,也就是xxx.__iter__(),被赋值给一个i。i = xxx.__iter__

那么这个i就成为一个迭代器。这个过程也可以表示为i = iter(xxx)

迭代器本身就有一个i.__next__方法,其实就相当于next(i)

  • Iterable:表示这个对象是“可以被迭代的”。比如说list、dict、str都是Iterable,但是根据上面代码可以发现,list、dict、str并不能算迭代器。
  • Iterator:迭代器对象。定义迭代器对象的时候,要有“数据流”的概念。迭代器和可迭代对象的差别就在于,迭代器是一个数据流,可以看成是一个有序序列,我们不知道序列的长度,只能通过next()函数对他进行不断迭代,直到爆出StopIteration的错误提示.

其实这个StopIteration并不能算一个错误,只是一个提示,表示这个迭代器已经被迭代完了。这时候就需要使用try……except……函数来规避掉这个错误,在出现StopIteration的时候自动跳出迭代。

可迭代对象转换成迭代器: iter()

小结:

凡是可以使用for循环的,都是Iterabale;

凡是可以使用next()方法的都是Iterator,迭代器代表一个惰性计算(无限有限皆可)序列,比方说:全体自然数集合就是一个Iterator;

一个迭代器被iter()之后,仍然是一个迭代器

for x in [1,2,3,4,5,6]:
    print (x)

print(‘======================‘)

it = iter([1,2,3,4,5,6])
while True:
    try:
        print(next(it))
    except StopIteration:
        break

以上两段代码输出结果相同,所以我们就能理解for方法的运行原理了

生成器

  • 关键字:yield

为什么要使用生成器,什么是生成器?

可循环的类型(Iteratable)比如list,占用内存空间较大,当你想要其中的一个元素的时候只能将list放在内存里,根据索引去获取,生成器本质就是一个迭代器,但是我们知道迭代器是可以循环出来的,每次都可以只输出一个值,节约了内存空间。

如果一个迭代对象,他后面的每个值都是可以根据一定计算获得的。我们是否可以在循环的过程中不断推算出后续的元素,这样就不必创建list,节省了大部分空间。

这种一边循环一边计算的机制,就是生成器:generator

如何创建一个生成器

  • 将列表生成式的[]改成()
>>> l = [x*x for x in range(10)]
>>> l
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> g = (x*x for x in range(10))
>>> g
<generator object <genexpr> at 0x0000006CC7E0D1A8>

列表生成后,list可以直接打印出来,但是generator的迭代器属性,每个元素需要用next()函数获取

 >>> next(g)
0
>>> next(g)
1
>>> next(g)
4
>>> next(g)
9

以上这种调用方式,手工操作部分太多了,所以正确的调用应该是使用for循环:

>>> next(g)
0
>>> next(g)
1
>>> next(g)
4
>>> next(g)
9
>>> for i in g:
…     print(i)
…
16
25
36
49
64
81

啊哈哈哈哈,侧面反映了迭代器的单项输出性质,只能迭代下一个。这里还可以注意到,用for调用generator的时候,不需要担心StopIteration错误

举个栗子:斐波拉契数列

函数生成斐波拉契数列,并注释逻辑:

# 函数生成斐波拉契数列
def fibonacci(max):
    n, a, b = 0, 0, 1   #这里的n用于计数,取最大数用
                        #a是前置数,用于配合b获取最开始的两个数字
                        #b是第一个数
    while n < max:      #当n未达到max的时候
        print(b)        #打印数字b,后面可以看到,b的值是前两个数相加的和
        a, b = b, a + b #对a,b分别进行赋值,a是原来的b,b是前两数相加获得后的和
        n = n + 1       #n的计数加一
    return ‘done‘

这时候执行fibonacci(num),得到的结果就是一个最大长度为num的fibonacci数列了。

而想要把这个函数改成一个generator,其实只要通过一句yield就可以:

# 函数生成斐波拉契生成器
def fibonacci(max):
    n, a, b = 0, 0, 1
    while n < max:
        yield b
        a, b = b, a + b
        n = n + 1
        return ‘done‘
print(fibonacci(10))

结果为:

<generator object fibonacci at 0x000000D75546D1A8>

Process finished with exit code 0

这里的fibonacci已经不再是一个函数了,而是一个生成器,他的结果需要被循环出来,而他本身就作为一个方法被储存在某个变量里。

f = fibonacci(6)
for i in f :
    print(i)

用yield返回结果的执行流程

使用了yield之后,函数就变成了生成器。我们原先认为函数运行到return、或者函数的最后一行语句就返回。但是添加了yield之后的生成器不一样,在每次调用了next()执行,遇到yield就返回,再次执行时,从上次返回的yield开始继续执行

在一个函数中,可以有多个yield,但是这种不太常见,在我的理解里,yield就有点像IDE当中的debug断点,在指定断点输出一个值。但是这个断点越多越复杂,反而会不好,因为他最终返回的生成器是一个同类数据流,当你这个数据流中的数据出现两种,就不太适合被再次加工了。

所以一般用一个yield来定义一束数据流(生成器),然后通过生成器具有的迭代器特性来逐个输出处理数据。起到节约内存的作用。

注意:yield有时候会作为一个传参工具(下文会细说,搭配.send()使用,被称为协程函数),这时候的yield会被放在一个函数中比较靠前位置,但是函数本身暂时没有可以输出的值,这时候就需要提前使用next()方法,将生成器初始化到yield的位置

比较优秀的方法,是使用装饰器,提前将函数next()或者g.send(None)一次,并且方便后面的生成器运行调用:

def init(func):
    def wrapper(*args,**kwargs):
        res = func(*args,**kwargs)
        next(res)
        return res
    return wrapper

@init

如果上次的yield是最后一个,并且生成器是一个被while包围的函数,就从上次结束的yield处继续下次循环,仍然遇到这个yield就输出返回。

>>> def odd():
…     print(‘step1‘)
…     yield 1
…     print(‘step2‘)
…     yield 2
…     print(‘step3‘)
…     yield 3
>>> o = odd()
>>> next(o)
step1
1
>>> next(o)
step2
2
>>> next(o)
step3
3

实验记录:

>>> def foo():
…     print(‘llllll‘)
…     yield 3
…     print(‘wwwwww‘)
  File "<stdin>", line 4
    print(‘wwwwww‘)
                  ^
IndentationError: unindent does not match any outer indentation level

yield后面跟返回的值如果不用括号括起来,容易报错,注意养成习惯

作业代码以及注释:

#模拟管道功能,将cat的处理结果作为grep的输入
#从文件中获取想要的数据
def cat(file_name):         #传参获得目标文件名
    with open(file_name,‘r‘) as f:
        res = iter(f.readlines())
    return res
#通过readlines方法获取文件的行集合list,
#并且将这个集合生成为一个迭代器返回给函数

#传参获取目标关键字,文件内容迭代器,
def grep(key_str,iterator):
    for i in iterator:
    #迭代文件内容并且匹配关键字
        if key_str in i.strip():
            #匹配到后输出关键字
            print (i.strip())

# 调用函数阶段:
# grep(‘apple‘,cat(‘a.txt‘))

协程函数

如果函数内yield的表达形式为var= yield,那么必须在往生成器函数中传参前,next(g)或者e.send(None)

因为协程函数中,需要将函数初始化暂停至yield所在点,然后再进行生成器轮巡运算。

面向过程编程

在提协程函数的时候,还需要提一个面向过程编程思想

流水式的变成思想,在设计程序时,需要把整个流程设计出来。

这种思想的优缺点:

  • 优点:

    体系结构更加清晰

    简化程序的复杂度

  • 缺点:

    可扩展性差,一条流程只能给一组功能使用。

作业以及代码注解:

# 定义一个可以不断传入(send方法)网址来进行爬取数据的生成器函数
# 定义一个配合协程函数的装饰器
def yield_next(func):
    def wrapper(*arg,**kwargs):
        res = func(*arg,**kwargs)
        next(res)
        #注意,这里仍然需要返回res给函数wrapper
        #如果缺少这步,下方调用return wrapper的时候无效
        return res
    return wrapper
#模块加载
from urllib.request import urlopen

@yield_next
def get_url():
    while True:
        url = yield
        #外部传值给yield 相当于yield 统一资源定位器,并且url能传入新的值
        url_res = urlopen(url)      #爬取url指定的页面内容
        webLine = url_res.read()    # 读取html页面
        print(webLine)              #输出url页面

g = get_url()
#由于之前使用了装饰器,这里不用next()
g.send(‘http://www.baidu.com‘)

典型范例以及代码解析:

实现linux中grep -rl ‘python‘ dir_path效果的代码

# 想了半天觉得这个代码好蠢,不放了。

列表生成式

范例:

l = [‘egg%s‘%i for i in range(100) if i >50]
print(l)

g = os.walk(c:\\scott)
l1 = [‘%s\\%s‘%(i[0],j)for i in g for j in i[-1]]

返回结果放在最前,列表生成的for运算放在中间,后面可加判断语句

如果直接调用这个列表生成式的结果给一个函数进行运算,可以不用添加[]

生成器表达式

就是把列表生成的[]换成()

l = (‘egg%s‘%i for i in range(100) if i >50)

作业和练习

# 今日作业
# 有两个列表,
# 分别存放来老男孩报名学习linux和python课程的学生名字

linux = [‘钢弹‘, ‘小壁虎‘, ‘小虎比‘, ‘alex‘, ‘wupeiqi‘, ‘yuanhao‘]
python = [‘dragon‘, ‘钢弹‘, ‘zhejiangF4‘, ‘小虎比‘]
# 问题一:得出既报名linux又报名python的学生列表
l1 = [x for x in linux if  x in python]
print (l1)
# 问题二:得出只报名linux,而没有报名python的学生列表
l2 = [x for x in linux if x not in python]
print(l2)
# 问题三:得出只报名python,而没有报名linux的学生列表
l3 = [x for x in python if x not in linux]
print(l3)

shares = {

    ‘IBM‘: 36.6,

    ‘lenovo‘: 27.3,

    ‘huawei‘: 40.3,

    ‘oldboy‘: 3.2,
    ‘ocean‘:20.1
}

# 问题一:得出股票价格大于30的股票名字列表
list_1 = [i for i in shares if shares[i]>30]
print(list_1)
# 问题二:求出所有股票的总价格
list_sum = sum (shares[i] for i in shares)
print(list_sum)

l = [10, 2, 3, 4, 5, 6, 7]

# 得到一个新列表l1,
# 新列表中每个元素是l中对应每个元素值的平方
l1 = [(x*x) for x in l]
print(l1)
# 过滤出l1中大于40的值,然后求和
l1_sum = sum(y for y in l1 if y >40)
print(l1_sum)
时间: 2024-10-24 20:23:34

10.迭代器/生成器/协程函数/列表生成器的相关文章

python_递归_协程函数(yield关键字)_匿名函数_模块

协程函数(yield) 协程函数:生成器:yield关键字的另一种用法 例:装饰器自动初始化函数(生成器函数)deco 1 yield的语句形式: yield 1 2 #yield的表达式形式: x=yield 3 4 5 6 #协程函数 7 8 def deco(func): 9 def wrapper(*args,**kwargs): 10 res=func(*args,**kwargs) 11 next(res) 12 return res 13 return wrapper 14 15

python基础----迭代器、生成器、协程函数

一.什么是迭代器协议 1.迭代器协议是指:对象必须提供一个next方法,执行该方法要么返回迭代中的下一项,要么就引起一个StopIteration异常,以终止迭代 (只能往后走不能往前退) 2.可迭代对象:实现了迭代器协议的对象(如何实现:对象内部定义一个__iter__()方法) 3.协议是一种约定,可迭代对象实现了迭代器协议,python的内部工具(如for循环,sum,min,max函数等)使用迭代器协议访问对象. 二,为什么要用迭代器 优点: 1:迭代器提供了一种不依赖于索引的取值方式,

【Python】【控制流程】【生成器 | 协程 | 期物 | 任务】对比与联系

Python 的 asyncio 类似于 C++ 的 Boost.Asio. 所谓「异步 IO」,就是你发起一个 IO 操作,却不用等它结束,你可以继续做其他事情,当它结束时,你会得到通知. Asyncio 是并发(concurrency)的一种方式.对 Python 来说,并发还可以通过线程(threading)和多进程(multiprocessing)来实现. Asyncio 并不能带来真正的并行(parallelism).当然,因为 GIL(全局解释器锁)的存在,Python 的多线程也不

007-迭代器-生成器-协程

迭代器 : 区分 : 可迭代 ---> 实现了__iter__() 的类 可迭代对象 ---> 实现了 __iter__() 的类的实例 迭代器(对象) ---> 实现了 __iter__() 和 __next__() 的类的实例 作用 : 一个可以记住遍历的位置的对象,真正能够获取位置内容的是 next() 如何自定义一个迭代器对象? 要创建两个类,一个普通类,一个迭代器类,合起来就是迭代器对象 定义一个普通类,在普通类中实现 __iter__(),--> 此时,普通类的对象就是

python协程函数、递归、匿名函数与内置函数使用、模块与包

目录: 协程函数(yield生成器用法二) 面向过程编程 递归 匿名函数与内置函数的使用 模块 包 常用标准模块之re(正则表达式) 一.协程函数(yield生成器用法二) 1.生成器的语句形式 a.生成器相关python函数.装饰器.迭代器.生成器,我们是如何使用生成器的.一个生成器能暂停执行并返回一个中间的结果这就是 yield 语句的功能 : 返回一个中间值给调用者并暂停执行. 我们的调用方式为yeild 1的方式,此方式又称为生成器的语句形式. 而使用生成器的场景:使用生成器最好的场景就

python-学习 协程函数 模块与包

一.协程函数 yield的用法: 1:把函数的执行结果封装好__iter__和__next__,即得到一个迭代器2:与return功能类似,都可以返回值,但不同的是,return只能返回一次值,而yield可以返回多次值3:函数暂停与再继续运行的状态是有yield保存 1 # 例子1 2 # def chi(name): 3 # print('%s 开始上菜啦~'%name) 4 # cd=[] #菜单 5 # while True: 6 # food=yield cd 7 # cd.appen

协程函数

1.协程函数吃包子简洁案例 下面代码中需要注意的是,yield的位置和作用,仔细看运行原理 def eater(name): print('%s start to eat food' %name)#第一个print while True: food=yield print('%s get %s ,to start eat'%(name,food))#第二个print print('done') e=eater('钢蛋')#e是生成器 print(e)#打印生成器的内存地址 print(next(

day05 协程函数,递归函数,匿名函数lambda,内置函数map reduce filter max min zip sorted,匿名函数lambda和内置函数结合使用,面向过程编程与函数编程,模块与包的使用,re模块内置函数

基础篇 本章大纲: 协程函数 递归函数 匿名函数lambda 内置函数map reduce filter  max min zip sorted 匿名函数lambda和内置函数结合使用 面向过程编程与函数编程 模块与包的使用 re模块内置函数 一,协程函数 注意:函数先定义,后使用.这是函数第一原则.函数主要分为定义,调用 1.1,什么是协程函数 协程函数特点:yield变为表达式,可以通过g.send(value)传值,用send传值时协程函数需要初始化,也可以说是生成器函数的一种 1.2,协

函数5—协程函数的有应用

协程函数的应用:找到一个文件夹下所有包含python字符串的文件的绝对路径为生成器函数添加初始化功能的装饰器import os ,timedef init(func): def wrapper(*args,**kwargs): res = func(*args,**kwargs) next(res) return res #返回初始化之后的生成器 return wrapper 下面的每一个函数都是一个生成器函数,可以接收生成器发送的值, 同时也是一个生成器,可以发送下面的函数想要的内容 可以实现