【菜鸟零基础学习笔记】Day18~19-迭代器协议、生成器表达式

【递归和迭代】

什么是递归?

A向B问路;

B对A说我不知道,但是C知道,我帮你去问问C;

C对B说我不知道,但是D知道,我帮你去问问D;

。。。。。。

Z对Y说我知道,往前走就行了;

Y对X说我知道,往前走就行了;

。。。。。。

B对A说我知道,往前走就行了。

(最后A问到路了)

 

什么是迭代?

A向B问路;

B对A说我不知道,但是C知道,你直接去问C;

C对A说我不知道,但是D知道,你直接去问D;

。。。。。。

Z对A说我知道,往前走就行了。

(最后A问到路了) 

迭代要基于上一次的状态

 

什么叫迭代器协议?

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

2、可迭代对象(就是迭代器)

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

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

 

字符串、列表、元组、字典、集合、文件对象,这些都不是可迭代对象,只不过在for循环时,调用了他们内部的_iter_()方法,把他们变成了可迭代对象。由于可迭代对象遵循迭代器协议,所以可以被for循环用__next__遍历。

x = 'hello'
iter_test = x.__iter__()      #iter_test是可迭代对象;这一步用iter(x)也能达到相同的效果
print(iter_test)              #<str_iterator object at 0x0000021A8DC86A58>
print(iter_test.__next__())   #h
print(iter_test.__next__())   #e
print(iter_test.__next__())   #l
print(iter_test.__next__())   #l
print(iter_test.__next__())   #o
print(iter_test.__next__())   #报错

 

for循环是怎么工作的?

l = [1,2,3]
for i in l:     #先通过i_l=l.__iter__()把l变成一个可迭代对象;实际上每次遍历都是在执行i_l.__next__()
    print(i)    #循环完毕后终止协议

for循环的强大之处在于,他不仅能遍历有序类型,也可以遍历无序类型;相比较之下,用索引无法遍历字典、集合等无序类型

 

迭代器--列表

l = ['a','b','c','d']      #列表不遵循迭代器协议
iter_l = l.__iter__()      #变成一个迭代器(只要遵循迭代器协议,就叫可迭代对象)
print(iter_l.__next__())   #a
print(iter_l.__next__())   #b
print(iter_l.__next__())   #c
print(iter_l.__next__())   #d

 

迭代器--文件处理

test2原文件:

你好
abc
1
2
3
4

举例:

f = open('test2','r+',encoding='utf-8')
iter_f = f.__iter__()
print(iter_f)                      #<_io.TextIOWrapper name='test2' mode='r+' encoding='utf-8'>
print(iter_f.__next__(),end='')    #你好
print(iter_f.__next__(),end='')    #abc
print(iter_f.__next__(),end='')    #1
print(iter_f.__next__(),end='')    #2

补充:

print(next(iter_l))的效果和iter_l.__next__()一样

t.send('123')  #可以传值给x = yield里面的x

 

【生成器】

什么是生成器?

 

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

 

2、生成器是可迭代对象

 

3、生成器在python中的表现形式(python有两种不同的方法提供生成器):

1)生成器函数:

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

举例1:

def test():
    yield 1
    yield 2
    yield 3
g = test()
print(g)              #<generator object test at 0x0000029AD136DC50>这是一个生成器
print(g.__next__())   #1
print(g.__next__())   #2
print(g.__next__())   #3

举例2:

def product_baozi():
    for i in range(100):
        print('正在生产包子')
        yield '包子%s' %i
        print('开始卖包子')
pro_g = product_baozi()
print(pro_g.__next__())
print(pro_g.__next__())


输出:
正在生产包子
包子0
开始卖包子
正在生产包子   #第二次循环开始
包子1

 

2)生成器表达式:

类似于列表推导,但是,生成器返回按需产生结果的一个对象,而不是一次构建一个结果列表(先看下面关于“三元表达式”和“列表解析”的介绍)

laomuji = ('鸡蛋%s' %i for i in range(10))   #生成器表达式
print(laomuji)              #<generator object <genexpr> at 0x0000027B2811DBF8>
print(laomuji.__next__())   #鸡蛋0
print(laomuji.__next__())   #鸡蛋1
print(laomuji.__next__())   #鸡蛋2
print(next(laomuji))        #鸡蛋3

 

4、生成器的好处

python使用生成器对延迟操作提供了支持,所谓延迟操作,是指在需要的时候才产生结果,而不是立即产生结果。这也是生成器的主要好处。

 

5、生成器小结:

1)是可迭代对象

2)实现了延迟计算,节省内存

3)生成器本质和其他的数据类型一样,都是实现了迭代器协议,只不过生成器附加了一个延迟计算省内存的好处,其余的可迭代对象没有这点好处

 

【三元表达式】

name = 'alex'
res = 'SB' if name == 'alex' else '帅哥'
print(res)   #SB

表示:如果name == 'alex'成立,就返回'SB';如果name == 'alex'不成立,就返回'帅哥'

注意:不要忘记加else

 

【列表解析】

举例:送十个鸡蛋

普通方法:

egg_list = []
for i in range(10):
    egg_list.append('鸡蛋%s' %i)
print(egg_list)

输出:
['鸡蛋0', '鸡蛋1', '鸡蛋2', '鸡蛋3', '鸡蛋4', '鸡蛋5', '鸡蛋6', '鸡蛋7', '鸡蛋8', '鸡蛋9']

 

下面用列表解析的方法实现相同的功能(一种简洁的生成列表的方式):
 

l = [i for i in range(10)]
print(l)   #[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
l = ['鸡蛋 %s' %i for i in range(10)]
print(l)   #['鸡蛋 0', '鸡蛋 1', '鸡蛋 2', '鸡蛋 3', '鸡蛋 4', '鸡蛋 5', '鸡蛋 6', '鸡蛋 7', '鸡蛋 8', '鸡蛋 9']
l = ['鸡蛋 %s' %i for i in range(10) if i>5]
print(l)   #['鸡蛋 6', '鸡蛋 7', '鸡蛋 8', '鸡蛋 9']
print(['a' for i in range(10)])   #['a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a']

 

总结:
1、把列表解析的[]换成()就是生成器表达式

2、列表解析与生成器表达式都是一种便利的编程方式,只不过生成器表达式更节省内存(因为生成器表达式是基于迭代器协议一个一个取值的),列表解析在数据量大的情况下显得比较笨重

a = sum((i for i in range(10000)))
print(a)   #49995000

如果要创建一个有10000个数的列表求和,使用迭代器会节约内存

3、python不但使用生成器协议让for循环变得更加通用,大部分内置函数,也是用迭代器协议访问对象的。例如,sum函数是python的内置函数,该函数使用迭代器协议访问对象,而生成器实现了迭代器协议。

 

一个例子:
 

def test():
    for i in range(4):
        yield i
t = test()
for i in t:
    print(i)   #0 1 2 3
t1 = (i for i in t)   #t1是用来遍历t的,由于生成器只能遍历一次,所以下面一行的运行结果为空列表
print(list(t1))   #[]

另一个例子:

def test():
    for i in range(4):
        yield i
t = test()
t1 = (i for i in t)   #得到一个生成器
print(list(t1))   #[0,1,2,3]

还有一个比较神奇的例子:

def test():
    for i in range(4):
        yield i
t = test()   #得到了一个生成器(生成器在产生的时候不会进行任何运行操作,只有执行next的时候才会运行一次)
t1 = (i for i in t)  #得到一个生成器,t1是一个ip地址,t1里面没有值(生成器在产生的时候不会运行)
t2 = (i for i in t1)
print(t1)
print(t2)
print(list(t1))   #遍历t1,相当于在对t进行next操作
print(list(t2))

输出:
[0, 1, 2, 3]
[]    #这里输出的是一个空列表!因为生成器只能被遍历一次!!

 

【生成器总结】

1、语法上和函数类似:

生成器函数和常规函数几乎是一样的,他们都是使用def语句进行定义,差别在于,生成器使用yield语句返回一个值,而常规函数使用return语句返回一个值

2、自动实现迭代器协议:

对于生成器,python会自动实现迭代器协议,以便应用到迭代背景中(如for循环,sum函数)。由于生成器自动实现了迭代器协议,所以,我们可以调用他的next方法,并且,在没有值可以返回的时候,生成器自动产生StopIteration异常

3、状态挂起:

生成器使用yield语句返回一个值,yield语句挂起该生成器函数的状态,保留足够的信息,以便之后从他离开的地方继续执行

 

举例:

人口普查(详见day19生成器特性阐释视频)很重要!!

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值