一、迭代器
(1)定义
标准解释:迭代器是 Python 中实现了迭代协议的对象,即提供__iter__()和 __next__()方法,任何实现了这两个方法的对象都可以被称为迭代器。
所谓__iter__(),即返回迭代器自身
所谓__next__(),即返回容器的下一个元素。
(是不是没看懂,别急,听下面的人话怎么说)
简单来说,你听我慢慢道来
首先,我们看python中的 for 循环:
for i in 对象
其中,对象必须是可迭代的,什么叫可迭代,就比如对象是[1,2,3],i会依次变为1,2,3,那么这个对象就是可迭代的,如果这个对象是33,i只会变成33,这行代码还会报错,这个对象就是不可迭代的
然后,我们来理解下这行代码的逻辑:
如果对象是[1,2,3],那么它是可以迭代的,而这个可以迭代的对象,就叫做__iter__( ) 方法。
所以这一点,我们可以这么理解,如果没有__iter__( )方法,那么对象就不可迭代,那么程序就会报错。
当 i 依次变为1,2,3的过程中,它能自己变,就是从1变成2,又从2变成3,这种可以自动下一个的过程,就叫做__next__()方法
那么迭代器我们就可以这么去理解了:
迭代器=__iter__()方法+ __next__()方法
即,一个对象,它具有__iter__()方法和__next__()方法,那么它就是迭代器
(注意,诸如__iter__()中左右横线是英文状态下的两个“_”连在一起的)
(2)使用
我们来看一个迭代器的例子,加深理解:
list = [1, 2, 3]# 用inter()方法获取迭代器对象
a = iter(list)# 使用迭代器遍历元素
print(next(a)) # 输出 1
print(next(a)) # 输出 2
print(next(a)) # 输出 3# 若再写一行print(next(a))
# 则下一次调用将会抛出 StopIteration 异常,因为列表中没有更多的元素了
解释:
我们首先定义一个列表,
然后用 iter()方法获取了可迭代对象
最后再用 next()方法进行了输出
注意:有人可能发现我们之前一直说的都是__iter__(),而这里写的却是 iter()
原因在于,这里代码中的
iter()
是一个函数,它用于调用该对象的__iter__()
方法,进而返回一个迭代器。
那么,什么时候写 iter( )什么时候写__iter__( ) 呢?
- 何时写 iter( ):
当你已有一个可迭代对象,并希望得到它的迭代器时,你可以直接使用 iter()
函数。
例如,你有一个列表,想获取它的迭代器。
- 何时写__iter__( ) :
你创建了一个类,并希望它可以被 for
循环遍历,或者可以被 next()
函数逐个获取元素
(这个新概念“类”,不懂可以先跳过,后面的章节会详细解释,什么叫“类”)
说起使用,这里还有个重要知识点:获取迭代器的值
在上面的例子中,我们可以看到获取迭代器的值,用的是 next(迭代器对象)
其实,我们还可以用 迭代器对象.__next__() 来获取,如:
补充::除此之外,还可以用一个报错的方法来输出
为啥我说报错呢,因为for循环时内部会报错,但是我们看不到而已。
(3)作用
我们使用时可以发现,迭代器输出时是一个一个往外蹦的,我们不用next方法,它就不会输出。
所以这里的一个重要作用,便是它只有在需要时才计算和返回下一个数据。
这意味着它们不会一次性计算出所有结果,而是每次调用时都产生下一个值,从而避免了不必要的计算和内存占用。
二、生成器
(1)定义
生成器,就是一个特殊的迭代器。
特殊在哪呢?
就在于,它多了个带
yield
关键字的函数
什么是yield关键字?
你可以把它理解为函数里的return
它长什么样?它长下面这样:
其中,gen就是一个生成器。
前面那个my_generator()函数,后面不是带了三个yield吗,所以给gen后,gen就变成生成器了。
(2)yield关键字
我们来详细解释下,这个yield关键字:
首先,我们要知道,使用
yield
关键字的函数会变成一个生成器,从此,它就不是普通的函数了。(yield可以有多个)
与return不同的是:
- 每次调用yield时,函数会暂停,并返回一个值。
即计算机执行完第一个print(next(gen))时,函数会将当前第一个值 1 返回,然后自身暂停到yield1和yield2之间。
执行第二个print(next(gen))时,函数就从新起步,然后返回当前第一个值 2 (因为它之前不是暂停到yield1和yield2之间了吗,所以此时2是第一个值),然后再次暂停到了yield2和yield3之间
依次类推,是谓函数会暂停,并返回一个值。
所以,到此,生成器的作用也就体现出来了,即:它可以暂停函数的执行,并在 需要时恢 复执行,不会一次性生成所有的数据,而是什么时候需要,什么时候 生成,从而节省内存。
(3)生成器的执行
说白了,就是输出值:
我们之前说了,生成器是特殊的迭代器,所以迭代器获取值的方法,生成器同样适用,即:
- next(生成器对象)
- 生成器对象.__next__()
- 用for遍历
我们来看一个复杂点的例子:
def fun1(L:list): #L:list 表明 L参数预期得到一个列表类型sum = 0for i in L:sum += iyield suml=[1,4,3]
print(list(fun1(l))) #list()将结果转换为易看懂的列表#输出[1, 5, 8]
当l列表中第一个元素 1 到函数fun1中时,sum=1 ,返回1,然后暂停
当l列表中第二个元素 4 到函数fun1中时,sum=1+4,返回5,然后暂停
当l列表中第三个元素 3 到函数fun1中时,sum=5+3,返回8
而,如果把上面的yield换成return的话,最终只会输出一个1,然后之间结束了(记得删掉list(),因为yield返回的是迭代对象,所以需要转化,而return的话则不需要)
(4)传值
前文我们说了不少获取值的方法,其实这里还有个传入值的方法,那就是send()函数
格式:生成器对象.send(value)
其中value这个参数可有,也可无。但作用很不同。
- 当无参数时,我们将value写作None,即 生成器对象.send(None)
此时,它和 next() 函数的功能完全相同,如图:
- 当有参数时,它会将暂停的程序继续执行,而 value 值会赋值给之前暂停 yield 。
def a():b = yield "hello" #此时b是第一个yield的接受者c = yield b #此时c是第二个yield的接受者yield cprint(b) #输出b的值print(c) #输出c的值yield "输出完毕"d = a()print(d.send(None))
print(d.send(1)) #将值1输送进去
print(d.send(2)) #将值2输送进去print("输出b、c的值:")
print(d.send(None))
输出:
hello
1
2
输出b、c的值:
1
2
输出完毕
解释:
执行print(d.send(None))时,相当于next方法,即将yield返回,所以首先输出hello。(注意此时b还没有被赋值,便已经暂停了。)
执行print(d.send(1))时,值1被赋值给被暂停的yield,所以成了b=1
然后继续执行代码c = yield b
此时遇到yield,所以输出b的值1,然后暂停。
执行print(d.send(2)),将刚才暂停的yield值赋值为2,所以成了c=2,然后继续执行到第三个yield语句,输出c的值。
最后,我们又用send检验了一下b和c的值,发现确实是这样。
(5)停止运行
如果说,我们想终止该生成器,让其后续将无法再调用 next() 函数 或者 __next__() 方法启动执行,否则抛出 StopIteration 异常
我们可以使用close()函数
如图
三、装饰器
(1)定义
标准解释:在Python中,装饰器(decorators)是一种高级功能,它允许你在不修改 原有函数或方法定义的情况下,给函数或方法添加新的功能。装饰器本质上 是一个函数,它接收一个函数作为参数,并返回一个新的函数或修改原来的 函数。
用人话来说,比如我们有一个a函数,a函数中有 b功能代码、c功能代码、d功能代码等等。
我们不想直接去修改b功能代码,因为c功能等可能跟b有关系,要修改b的话,可能整个a函数的各板块都要大改。
所以我们可以用装饰器说明一下,即,在别处写好一个n功能代码,然后使用装饰器,让n功能代码平替了b功能代码,这样就不用大改了。
(2)格式
格式: @须装饰的函数名
此时,我们称那个被装饰的函数名为装饰器
除此之外,装饰器函数一般需要函数闭包形式实现。
言内之意,就是装饰器必须是函数嵌套那样的。
(说一般,是因为还可以使用类来实现装饰器,后续文章再详细介绍)
(3)用法
比如,我们有n、c、d功能代码,我们想计算这些代码的运行时间
如果没用装饰器,我们可能会写:
import time
def a1(m):time1=time.time()b功能代码time2=time.time()time0=time2-time1 return time0
def a2(m):time1=time.time()c功能代码time2=time.time()time0=time2-time1 return time0
def a3(m):time1=time.time()d功能代码time2=time.time()time0=time2-time1 return time0
print (a1(True))
print (a2(True))
print (a3(True))
如果用上装饰器,就会简单很多,
import time
def a(m):def n(): #函数嵌套,即闭包time1=time.time() #起始那会的时间m()time2=time.time() #结束那会的时间time0=time2-time1 #计算时间差return time0return n@a #启用装饰器,会将b函数平替m()
def b(x):代码
print (a(True)) #输出平替后的运行b函数的时间@a
def c(x):代码
print (a(True))@a
def d(x):代码
print (a(True))