Python学习(迭代器、生成器、装饰器)
文章目录
一、迭代器
1.1 可迭代对象
list、str、tuple… -----> for … in … 遍历 ---->遍历(迭代)
1.2 迭代器协议
对象必须提供一个next方法,执行该方法,要么返回迭代中的下一项,要么引起StopIteration异常,以终止迭代(只能往下走,不能回退)
现在,我们就可以说,实现了迭代器协议的对象就是可迭代对象。
通过在对象内部定义一个
__iter__方法
li = [1, 2, 3] sr ="abcd" # print(li.__iter__()) # print(sr.__iter__()) li_iter = li.__iter__() sr_iter = iter(sr) print(li_iter.__next__()) print(next(li_iter)) print(li_iter.__next__()) print(next(sr_iter)) print(sr_iter.__next__())
1.3 可迭代对象的测试
collections
使用isinstance()来判断一个对象是否可迭代
from collections import Iterable #使用isinstance()来判断一个对象是否可迭代 print(isinstance([],Iterable)) #True print(isinstance(str(),Iterable)) #True print(isinstance({},Iterable)) #True print(isinstance(set(),Iterable)) #True print(isinstance(123,Iterable)) ##False print(isinstance(True,Iterable)) #False
自定义一个类,其可以容纳数据,测试该类的可迭代性
from collections import Iterable class MyClass: def __init__(self): self.names = [] def add(self, name): self.names.append(name) my_class = MyClass() my_class.add("Tom") my_class.add("Jack") my_class.add("Lucy") print("是否为可迭代对象:", isinstance(my_class, Iterable)) #False
回顾之前说的
__iter__方法,其可以为我们提供一个迭代器。在迭代一个可迭代对象的时候,实际上就是获取该对象提供一个迭代器。然后提供该迭代器依次获取对象的每一个数据
from collections import Iterable class MyClass: def __init__(self): self.names = [] def add(self, name): self.names.append(name) def __iter__(self): return self.names.__iter__() print("是否为可迭代对象:", isinstance(my_class, Iterable)) #True for i in my_class: print(i, end=" ")
1.4 for item in 可迭代对象 循环的本质
就是通过iter()函数获取可迭代对象的Iterable的迭代器,然后对获取到的迭代器不断调用next()方法来获取下一个值并将其赋值,当遇到StopIteration的异常后,退出
1.5 应用场景
迭代器的核心就是通过next()函数调用返回下一个数据值。如果每次返回的数据值不是在一个已有的数据集合中读取的,而是通过程序按照一定的规律计算生成。那么也就意味着可以不用依赖一个已有的数据集合,namely,无需将所有的迭代对象数据一次性缓存下来供后续使用。这样可以节省大量的存储(内存)空间。
如
斐波那契数列
现在我们希望通过for…in…的方式来遍历斐波那契数列中的前n个数。
通过迭代来实现,每次迭代都可以
class FibIterator(object): ''' 斐波那契数列迭代器 ''' def __init__(self, n): # 记录生成的斐波那契数列的个数 self.n = n # 记录当前记录的索引 self.current_index = 0 # 记录两个初始值 self.num1 = 0 self.num2 = 1 def __next__(self): '''调用next()来获取下一个数''' if self.current_index < self.n: num = self.num1 self.num1, self.num2 = self.num2, self.num2 + self.num1 self.current_index += 1 return num else: raise StopIteration def __iter__(self): return self fib = FibIterator(10) for num in fib: print(num,end=" ")
二、生成器
生成器,利用迭代器,我们可以在每次迭代获取数据时(通过next()方法)按照特定的规律进行生成。但是我们在实现一个迭代器时,关于当前迭代的状态需要我们自己记录,进而才能根据当前的状态生成下一个数据。为了达到记录当前状态,并配合next()函数进行迭代使用,可以采用更简便的语法。
即生成器(generator),生成器是一种特殊的迭代器,它比迭代器更优雅
2.1 创建一个生成器
列表[ ] ---->()
li = [x**2 for x in range(6)] print(li) gen = (x**2 for x in range(6)) print(gen) print("通过next()函数取得下一个值") print(next(gen)) print(next(gen)) print(next(gen)) print(next(gen)) print(next(gen)) print(next(gen)) #print("通过for遍历") #for i in gen: # print(i,end=" ")
2.2 生成器函数
在函数中如果出现了yield关键字,那么该函数就不再是一个普通函数,而是一个生成器函数
如
def foo(): yield 1 yield 2 return yield 3 f = foo() print(next(f)) #程序会停留在对应的yield后的语句 print(next(f)) #当程序遇到return,return后的语句不会执行 print(next(f)) #StopIteration
next 和 yield进行匹配,如果遇到return,return后的语句不会执行,直接抛出异常StopIteration,终止迭代
在一个生成器中,如果return后有返回值,那么这个值就是异常的说明,而不是函数的返回值
构造一个产生无穷奇数的生成器
def odd(): n = 1 while True: yield n n += 2 odd_num = odd() # print(odd_num) # count = 0 # for i in odd_num: # if count >5: # break # print(i) # count += 1 # print(next(odd_num)) # print(next(odd_num)) for i in range(0,6): print(next(odd_num))
通过类手动编写迭代器,实现类似的效果
class OddIter: def __init__(self): self.start = -1 def __iter__(self): return self def __next__(self): self.start += 2 return self.start odd = OddIter() for i in range(6): print(next(odd))
2.3 生成器支持的方法
Help on generator object: odd = class generator(object) | Methods defined here: | | __del__(...) | | __getattribute__(self, name, /) | Return getattr(self, name). | | __iter__(self, /) | Implement iter(self). | | __next__(self, /) | Implement next(self). | | __repr__(self, /) | Return repr(self). | | close(...) | close() -> raise GeneratorExit inside generator. | | send(...) | send(arg) -> send 'arg' into generator, | return next yielded value or raise StopIteration. | | throw(...) | throw(typ[,val[,tb]]) -> raise exception in generator, | return next yielded value or raise StopIteration. | | ---------------------------------------------------------------------- | Data descriptors defined here: | | gi_code | | gi_frame | | gi_running | | gi_yieldfrom | object being iterated by yield from, or None
- close():手动关闭生成器函数,后面调用会直接引起StopIteration异常
- send(): x = yield y 语句的含义:send() 的作用就是使x赋值为其所传送的值(send的参数),然后让生成器执行到下一个yield
- 如果生成器未启动,则必须在使用send()前启动生成器,而启动的方法可以是gen.next(),也可以是gen.send(None)执行到第一个yield处。之后就可以使用send(para)不断传入值
- 如果已启动,则send(para)的作用就是给x赋值为所发送的值(send的参数),然后生成器执行到下一个yield
#close() def gen(): yield 1 yield 2 yield 3 yield 4 g = gen() print(next(g)) print(next(g)) g.close() print(next(g)) #send() def gen(): value = 0 while True: receive = yield value if receive == 'e': break value = "got:%s" % receive g = gen() print(g.send(None)) # send() 的作用就是使receive赋值为其所传送的值,然后让生成器执行到下一个yield print(g.send("aaa")) print(g.send(123)) print(g.send('e')) # StopIteration #throw() def gen(): i = 0 while i < 5: temp = yield i print(temp) i += 1 obj = gen() print(next(obj)) print(next(obj)) print(obj.throw(Exception,"Method throw called"))
三、闭包
闭是封闭(函数中的函数),包是包含(该内部函数对外部函数作用域而非全局作用域变量的引用)
闭包:
- 内部函数对外部函数作用域里的变量的引用
- 函数内的属性,都是有生命周期,都是在函数执行期间
- 闭包内的闭包函数私有化了变量,完成了数据的封装,类似面向对象
如
def foo(): print("in foo()") def bar(): print("in bar()") # 1.直接运行内部函数bar(),报错 # 2.先运行外部函数foo(),再运行内部函数bar(),依然报错
由于作用域的问题,函数内的属性,都是有生命周期的,只有在函数执行期间
再考虑这段代码,只有调用foo()时,内部的print()及bar()才能存活
现在我们为了让foo()内的bar()存活,就是调用bar(),我们该怎么做?
把bar()函数返回给函数
def foo(): print("in foo()") def bar(): print("in bar()") return bar var = foo() var() ''' in foo() in bar() '''
前面说,内部函数对外部函数作用域变量的引用---->如果是变量呢?
def foo(): a = 66 print("in foo()") def bar(num): print("in bar()") print(a + num) return bar var = foo() var(22)
四、装饰器
首先,看一个例子
@func1 def func(): print('aaa')
4.1 装饰器存在的意义
- 不影响原有函数的功能
- 可以添加新的功能
4.2 用法
一般常见的,比如拿到第三方的API接口,第三方不允许修改这个接口,这个时候,装饰器就派上用场
装饰器本身也是一个函数,作用是为现有的函数,在不改变函数的基础上,增加一些功能进行装饰
它是以闭包的形式去实现的
在使用装饰器函数时,在被装饰的函数的前一行,使用
@装饰器函数名的形式来进行装饰
如:
现在在一个项目中,有很多函数,由于我们的项目越来越大,功能也越来越多,导致程序越来越慢
其中一个功能函数的功能是实现100万次的累加
def my_count(): s = 0 for i in range(1000001): s += i print("sum:",s)
假如我们要计算函数的运行时间
import time def my_count(): s = 0 for i in range(1000001): s += i print("sum:", s) start = time.time() my_count() end = time.time() print("执行时间为:", end - start)
实现了时间的计算,但如果有成千上万个函数,每个函数这么写一遍,非常麻烦,代码量也凭空增加很多
明显不符合开发原则,代码太过冗余。考虑将上述代码中的时间计算改写进函数
import time def my_count(): s = 0 for i in range(1000001): s += i print("sum:", s) def count_time(func): start = time.time() func() end = time.time() print("执行时间为:", end - start) count_time(my_count) #更改了调用方式
这样做仍然影响了原来的使用
思考能不能在使用时不影响函数的原来使用方式,同时实现时间计算
考虑闭包
import time def count_time(func): def wrapper(): start = time.time() func() end = time.time() print("执行时间为:", end - start) return wrapper def my_count(): s = 0 for i in range(1000001): s += i print("sum:", s) my_count = count_time(my_count) my_count()
让my_count函数重新指向count_time函数返回的函数引用。这样使用my_count时,就和原来的方式一样了
采用装饰器
import time def count_time(func): def wrapper(): start = time.time() func() end = time.time() print("执行时间为:", end - start) return wrapper @count_time def my_count(): s = 0 for i in range(1000001): s += i print("sum:", s) my_count()
这样实现的好处,定义闭包函数后,只需要通过
@装饰器函数名的形式的装饰器语法,就可以将
@装饰器函数名加到要装饰的函数前即可
这种不改变原有函数功能,对函数进行拓展的形式,就称为
装饰器
在执行
@装饰器函数名时,就是讲原函数传递到闭包中。然后,原函数的引用指向闭包返回的装饰过的内部函数的引用
4.3 形式
无参数无返回值
def setFunc(func): def wrapper(): print("start") func() print("end") return wrapper @setFunc def show(): print("show") show() ''' start show end '''
无参数有返回值
def setFunc(func): def wrapper(): print("start") return func() print("end") #遇到return,此句不执行 return wrapper @setFunc def show(): return "show" print(show()) ''' start show '''
有参数无返回值
def setFunc(func): def wrapper(s): print("start") func(s) print("end") return wrapper @setFunc def show(s): print("hello %s" % s) show("city college") ''' start hello city college end '''
有参数有返回值
def setFunc(func): def wrapper(x,y): print("start") return func(x,y) return wrapper @setFunc def myAdd(x,y): return x + y print(myAdd(1,2)) ''' start 3 '''
万能装饰器
根据被装饰函数的定义不同,分出了四种形式
能不能实现一种适用于任何形式函数定义的装饰器
通过改变*args和**kwargs来改变传入参数的个数
def setFunc(func): def warpper(*args,**kwargs): print("warpper context.") return func(*args,**kwargs) return warpper #有参数无返回值 @setFunc def func(name,age): print(name,age) func("Tom",20) # warpper context. # Tom 20 #无参数无返回值 @setFunc def func2(): print("func2") func2() # warpper context. # func2 #无参数有返回值 @setFunc def func3(): return "func3" print(func3()) # warpper context. # func3 #有参数有返回值 @setFunc def func4(a,b): return a + b print(func4(1,2)) # warpper context. # 3 #不定参数 @setFunc def func5(a,b,*c,**d): print((a,b)) print(c) print(d) func5("city","college",1999,7,school = "zucc") # warpper context. # ('city', 'college') # (1999, 7) # {'school': 'zucc'}
4.4 函数被多个装饰器所装饰
一个函数在使用时,通过一个装饰器来拓展,并不能达到预期
一个函数被多个装饰器所装饰
#装饰器1 def setFunc1(func): def wrapper1(*args,**kwargs): print("wrapper context 1 start".center(40,"-")) func(*args,**kwargs) print("wrapper context 1 end".center(40,"-")) return wrapper1 #装饰器2 def setFunc2(func): def wrapper2(*args,**kwargs): print("wrapper context 2 start".center(40,"-")) func(*args,**kwargs) print("wrapper context 2 end".center(40,"-")) return wrapper2 @setFunc1 @setFunc2 def show(*args,**kwargs): print("show run".center(40)) show() ''' --------wrapper context 1 start--------- --------wrapper context 2 start--------- show run ---------wrapper context 2 end---------- ---------wrapper context 1 end---------- '''
程序语句运行顺序来看,从下往上去装饰
从函数的调用来看,从内往外去装饰
总结:
- 函数可以像普通变量一样,作为函数的参数或者返回值进行传递
- 函数的内部可以定义另外一个函数。目的,隐藏函数功能的实现
- 闭包实际上也是函数定义的一种形式
- 闭包定义的规则,在外部函数内定义一个内部函数,内部函数使用外部函数的变量,并返回内部函数的引用
- python中,装饰器就是用闭包来实现的
- 装饰器的作用,在不改变现有函数的基础上,为函数增加功能
- 装饰器的使用,通过
@装饰器函数名
的形式来给已有函数进行装饰,添加功能 - 装饰器四种形式,根据参数的不同以及返回值的不同
- 万能装饰器,通过可变参数(*args和**kwargs)来实现
- 一个装饰器可以为多个函数提供装饰功能
- 一个函数也可以被多个装饰器所装饰(了解)
- 通过类实现装饰器,重写
__init__
和__call__
函数 - 类装饰器在装饰函数后,原来的引用不再是函数,而是装饰类的对象
- python 学习2:生成器,迭代器,装饰器
- python 学习2:生成器,迭代器,装饰器
- python学习笔记四 迭代器,生成器,装饰器(基础篇)
- python学习-Day15-python生成式和生成器、迭代器、装饰器
- Python学习之三大名器-装饰器、迭代器、生成器 推荐
- python学习第四章-迭代器与生成器
- 装饰器、生成器、迭代器、及python中内置函数的使用
- Python学习笔记3 流程控制、迭代器、生成器
- 【Python学习日记】生成器 和 迭代器
- python学习笔记-生成器,迭代器,协程定义
- Python3学习笔记:迭代器与生成器
- python中的迭代器和生成器学习
- Python学习笔记--迭代器&生成器
- [python自学笔记]生成器、迭代器、装饰器
- python开发学习-day04(迭代器、生成器、装饰器、二分查找、正则)
- 循序渐进Python3(四) -- 装饰器、迭代器和生成器
- python学习 生成器 列表生成式 迭代器
- python学习笔记之八:迭代器和生成器
- Python学习笔记——迭代器和生成器
- python学习-38迭代器和生成器