【递归和迭代】
什么是递归?
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生成器特性阐释视频)很重要!!