迭代器,生成器, 闭包
1.迭代器
1. 可迭代对象
1. 可迭代对象的定义
- 字面意思:
- 对象:在Python中一切皆对象。就是一个实实在在的值。
- 可迭代:更新迭代,重复的,循环的一个过程,更新迭代每次都有新的内容。
- 可迭代对象: 可以进行循环更新的一个实实在在的值。
- 专业角度:可迭代对象就是内部含有__iter__ 方法的对象。
2. 获取对象的所有方法
str1 = "hello " print(dir(str1)) # 输出结果: ['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__',
'__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__',
'__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__',
'__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__',
'__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__',
'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find',
'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isdecimal', 'isdigit', 'isidentifier',
'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust',
'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition',
'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']
3. 判断一个对象是否是可迭代对象
st1 = "hello" print("__iter__" in dir(st1)) # True lis = [11,22,33] print("__iter__" in dir(lis)) # True
4. 可迭代对象的总结:
- 字面意思:可以进行循环更新的一个实实在在的值。
- 专业角度:内部还有__iter__ 方法的对象就是可迭代对象。
- 判断一个对象是不是可迭代对象:
__iter__ in dir(对象);
- 优点:
1. 存储的数据能显示,比较直观。
2. 拥有的方法比较多,操作方便。
- 缺点:
1. 占用内存
2. 不能直接通过for循环吗,不能直接取值(索引,key)
2. 迭代器:
1.迭代器的定义:
- 字面意思: 可更新迭代的工具
- 专业角度:内部还有__iter__方法并且含有__next__ 方法的对象就是迭代器
- 判断是否是迭代器:
'__iter__' and '__next__' in dir(对象)
2.判断一个对象是否是迭代器
with open("a.txt",mode="w",encoding="utf-8") as f: print(("__iter__"in dir(f)) and ("__next__"in dir(f))) # True
3. 迭代器的取值
4. 可迭代对象如何转换成迭代器
lis = [11,22,]
iter(lis)
5.while循环模拟for循环机制
lis = [11,22,33,44,55,66,77,88,99,14,12,15] obj = iter(lis) # 将可迭代对象转化成迭代器 while True: try: print(next(obj)) except StopItertion: break
6. 迭代器的总结:
- 字面意思:可更新迭代的工具
- 专业角度:内部还有__iter__ 方法并且含有__next__方法的对象就是迭代器
优点:
1.节省内存
2. 惰性机制,next一次,取一次值
缺点:
1.速度慢
2.不走回头路
7. 可迭代对象与迭代器的对比
1. 可迭代对象是一个操作方法比较多,比较直观,存储数据相对少(几百万个对象,8G内存是可以承受的)的一个数据集。 2. 当你侧重于对于数据可以灵活处理,并且内存空间足够,将数据集设置为可迭代对象是明确的选择。 3. 迭代器是一个非常节省内存,可以记录取值位置,可以直接通过循环+next方法取值,但是不直观,操作方法比较单一的数据集。 4. 当你的数据量过大,大到足以撑爆你的内存或者你以节省内存为首选因素时,将数据集设置为迭代器是一个不错的选择。
2. 生成器
1. 初始生成器;
什么是生成器?这个概念比较模糊,各种文献都有不同的理解,但是核心基本相同。生成器的本质就是迭代器,在python社区中,大多数时候都把迭代器和生成器是做同一个概念。不是相同么?为什么还要创建生成器?生成器和迭代器也有不同,唯一的不同就是:迭代器都是Python给你提供的已经写好的工具或者通过数据转化得来的,(比如文件句柄,iter([1,2,3])。生成器是需要我们自己用python代码构建的工具。最大的区别也就如此了。
2. 获取方式
1.生成器函数
2. 生成器推导式
3. Python内部提供的(内置函数或者模块)
3.生成器函数
我们先来研究通过生成器函数构建生成器
首先,我们先看一个简单的函数
def func(): print(11) return 22 ret = func() print(ret) # 运行结果: 11 22
将函数中的return换成yield,这样func就不是函数了,而是一个生成器函数
def func(): print(11) yield 22
我们这样写没有任何的变化,这是为什么呢? 我们来看看函数名加括号获取到的是什么?
def func(): print(11) yield 22 ret = func() print(ret) # 运行结果: <generator object func at 0x000001A575163888>
运行结果为什么不一样呢?由于函数中存在yield,那么这个函数就是一个生成器函数.
当我们再次执行这个函数的时候,就不再是函数的执行了,而是获取这个生成器对象,那么生成器对象如何取值呢?
之前我们说了,生成器的本质就是迭代器.迭代器如何取值,生成器就如何取值。所以我们可以直接执行next()来执行以下生成器
def func(): print("111") yield 222 gener = func() # 这个时候函数不会执⾏. ⽽是获取到⽣成器 ret = gener.__next__() # 这个时候函数才会执⾏ print(ret) # 并且yield会将func生产出来的数据 222 给了 ret。 # 执行结果: 111 222
生成器函数中可以写多个yield。
def func(): print(123) print(666) yield 222 a = 1 b = 2 c = a + b print(c) yield 88 ret = func() print(next(ret)) print(next(ret)) # 执行结果 # 123 # 666 # 222 # 3 # 88 # 一个next 对应一个yield
yield和return 的区别:
return:一般在函数中只能设置一个,它的作用是终止函数,并且把返回值给函数的调用者。
yield:只要函数中有yield,那么就是生成器函数。生成器函数中可以存在多个yield,yield不会终止函数,一个yield对应一个next。
举例:吃包子练习
我们来看一下这个需求:向楼下卖包子的老板订购了1000个包子.包子铺老板非常实在,一下就全部都做出来了
def eat(): lst = [] for i in range(1, 1000): lst.append('包子' + str(i)) return lst e = eat() print(e)
这样做没有问题,只吃了200个左右,剩下的800个,就只能占着一定的空间,放在一边了。如果包子铺老板效率够高,我吃一个包子,你做一个包子,那么这就不会占用太多空间存储了,完美。
def eat(): for i in range(1,1000): yield '包子'+str(i) e = eat() for i in range(200): next(e)
这两者的区别:
第一种是直接把包子全部做出来,占用内存。
第二种是吃一个生产一个,非常的节省内存,而且还可以保留上次的位置。
def eat(): for i in range(1,10000): yield '包子'+str(i) e = eat() for i in range(200): next(e) for i in range(300): next(e) # 多次next包子的号码是按照顺序记录的。
4.yield from
在python3中提供一种可以直接把可迭代对象中的每一个数据作为生成器的结果进行返回
# 对比yield和yield from的区别 def func(): lis = [1,2,3,4,5] yield lis ret = func() print(next(ret)) # 返回一个列表 # 执行结果: # [1,2,3,4,5]
def func(): l1 = [1, 2, 3, 4, 5] yield from l1 ret = func() print(ret) # 他会将这个可迭代对象(列表)的每个元素当成迭代器的每个结果进行返回。 for i in range(5): print(next(ret)) # 执行结果 # 1 # 2 # 3 # 4 # 5
有个小坑,yield from 是将列表中的每一个元素返回,所以 如果写两个yield from 并不会产生交替的效果
def func(): l1 = [1, 2, 3, ] lis1 = [7, 8, 9] yield from l1 yield from lis1 ret = func() print(ret) for i in range(6): print(next(ret)) # 执行结果: 1 2 3 7 8 9
从更深层的角度去理解yield from 有两个作用
1. 他可以完全代替内存循环,提高效率,让代码读起来更加顺畅
2. 还可以创建通道,把内存生成器直接与外层生成器的客服端连接起来
3.闭包
1.闭包的定义:
1. 闭包只能存在嵌套函数中
2. 内层函数对外层函数非全局变量的引用,就会形成闭包
3. 被引用的非全局变量也称自由变量,这个自由变量与内层函数产生一个绑定关系。
4. 自由变量不会再内存中消失
2.闭包的作用:
保证局部信息不被销毁,保证数据的安全性。
如何判断一个嵌套函数是不是闭包
1.闭包只能存在嵌套函数中
2. 内层函数对外层函数非全局变量的引用(使用),就会形成闭包。
3.判断函数是否会形成闭包
def func(): a = 1 def inner(): print(a) return inner ret = func() # 是闭包 a = 2 def func(): def inner(): print(a) return inner ret = func() # 不是闭包,a是全局变量 def func(a,b): def inner(): print(a) print(b) return inner a = 2 b = 3 ret = func(a,b) print(ret()) 就相当于这样 def func(a,b): a = 2 b = 3 def inner(): print(a) print(b) return inner ret = func(a,b) print(ret())
4. 如何用代码判断是闭包
def func(a,b): def inner(): print(a) print(b) return inner a = 2 b = 3 ret = func(a,b) print(ret.__code__.co_freevars)
# ('a', 'b')