装饰器,迭代器,生成器分析
装饰器
什么是装饰器
装饰器本身可以是任意可调用的对象=>函数
被装饰的对象也可以是任意可调用的对象=>函数
一个装饰器是一个需要另一个函数作为参数的函数。在装饰器内部动态定义一个函数:wrapper(原意:包装纸)。这个函数将被包装在原始函数的四周,因此就可以在原始函数之前和之后执行一些代码.
为何要用装饰器
开放封闭原则: 软件一旦上线就应该对修改封闭,对扩展开放
对修改封闭:
1. 不能修改功能的源代码
2. 也不能修改功能的调用方式
对扩展开发 可以为原有的功能添加新的功能
装饰器就是要在不修改功能源代码以及调用方式的前提下为原功能添加额外新的功能
无参装饰器
def outter(func): def wrapper(*args,**kwargs): res=func(*args,**kwargs) return res return wrapper
简单装饰器
import time def index(): print('welcome to index page') time.sleep(3) return 123 def home(name): print('welcome %s to home page' %name) time.sleep(1) def outter(func): # func=最原始那个home的内地址 def wrapper(*args,**kwargs): start=time.time() res=func(*args,**kwargs) stop=time.time() print('run time is %s' %(stop-start)) return res return wrapper index=outter(index) #index=outter(最原始那个index的内地址) #index=wrapper函数的内地址 home=outter(home) #index=outter(最原始那个home的内地址) #home=wrapper函数的内地址 home('egon') #wrapper('egon') index() #wrapper() ''' welcome egon to home page run time is 1.0155680179595947 welcome to index page run time is 3.015437364578247 '''
装饰器的语法糖
@装饰器的名字:要在被装饰对象正上方单独一行写上 import time def timmer(func): # func=最原始那个home的内地址 def wrapper(*args,**kwargs): start=time.time() res=func(*args,**kwargs) stop=time.time() print('run time is %s' %(stop-start)) return res wrapper.__doc__=func.__doc__ wrapper.__name__=func.__name__ return wrapper @timmer #index=timmer(index) ##index=timmer(最原始那个index的内地址) #index=wrapper函数的内地址 def index(): """这是index功能""" print('welcome to index page') time.sleep(3) return 123 @timmer #home=timmer(home) #index=timmer(最原始那个home的内地址) #home=wrapper函数的内地址 def home(name): """这是home功能""" print('welcome %s to home page' %name) time.sleep(1) # home('egon') #wrapper('egon') # index() #wrapper() # print(help(index)) # print(help(home)) # print(index.__doc__) print(index.__name__) ''' from functools import wraps import time def timmer(func): # func=最原始那个home的内地址 @wraps(func) def wrapper(*args,**kwargs): start=time.time() res=func(*args,**kwargs) stop=time.time() print('run time is %s' %(stop-start)) return res return wrapper @timmer def index(): """这是index功能""" print('welcome to index page') time.sleep(3) return 123 @timmer def home(name): """这是home功能""" print('welcome %s to home page' %name) time.sleep(1) print(help(index)) print(index.__name__)
有参装饰器
我们通过闭包知道参数可以嵌套函数里实现隐藏,并且实现全局参数的功能,与函数一起绑定
#参数默认为空字符串 def title(show=''): def printStar(func): def f(a,b): print(show,"*************************") return func(a,b) return f return printStar @title('add') def add(a,b): return a+b @title() def sub(a,b): return a-b print(add(1,1)) print(sub(2,1)) ''' add ************************* 2 ************************* 1 '''
def outter2(xxx,yyy): def outter(func): def wrapper(*args,**kwargs): res=func(*args,**kwargs) print(xxx) print(yyy) return res return wrapper return outter import time user_info={'current_user':None} def auth2(engine='file'): def auth(func): def wrapper(*args,**kwargs): if user_info['current_user'] is not None: res=func(*args,**kwargs) return res inp_user=input('username>>>: ').strip() inp_pwd=input('password>>>: ').strip() if engine == 'file': print('基于文件的认证') if inp_user == 'egon' and inp_pwd == '123': # 记录登录状态 user_info['current_user']=inp_user print('login successful') res=func(*args,**kwargs) return res else: print('user or password error') elif engine == 'mysql': print('基于mysql数据的认证') ''' elif engine == 'ldap': print('基于ldap的认证') else: print('无法识别认证源') ''' return wrapper return auth @auth2(engine='mysql') # @auth ===> index=auth(最原始那个index的内存地址)===》index=wrapper def index(): """这是index功能""" print('welcome to index page') time.sleep(2) return 123 @auth2(engine='file') def home(name): """这是home功能""" print('welcome %s to home page' %name) time.sleep(1) index() #wrapper() home('joek')
这时我们可以发现函数传送参数是修改def f(a, b),就是第三层的函数
叠加多个装饰器
当一个被装饰的对象同时叠加多个装饰器时
装饰器的加载顺序是:自下而上
装饰器内wrapper函数的执行顺序是:自上而下 忘记的话可以借助pycharm的断点分析,看一下加载与执行
import time def timmer(func): #func=wrapper2的内存地址 def wrapper1(*args, **kwargs): print('===================================>wrapper1运行了') start=time.time() res = func(*args, **kwargs) #===========================>跳到wrapper2去执行了, stop=time.time() print('run time is %s' %(stop - start)) return res return wrapper1 def auth(engine='file'): def xxx(func): # func=最原始那个index的内存地址 def wrapper2(*args, **kwargs): print('===================================>wrapper2运行了') name=input('username>>>: ').strip() pwd=input('password>>>: ').strip() if engine == 'file': print('基于文件的认证') if name == 'egon' and pwd == '123': print('login successfull') res = func(*args, **kwargs) return res elif engine == 'mysql': print('基于mysql的认证') elif engine == 'ldap': print('基于ldap的认证') else: print('错误的认证源') return wrapper2 return xxx @timmer # index=timmer(wrapper2的内存地址) #index=wrapper1的内存地址 @auth(engine='file') #@xxx #index=xxx(最原始那个index的内存地址) #index=wrapper2的内存地址 def index(): print('welcome to index page') time.sleep(2) index() #wrapper1的内存地址() ''' @auth(engine='file') #装饰器的顺序不一样结果不一样,上一个的运行时间包含了 @timmer #另外一个装饰器的运行时间 def index(): print('welcome to index page') time.sleep(2) index() #wrapper1的内存地址()
迭代器
什么是迭代器
迭代指的是一个重复的过程,每一次重复都是基于上一次的结果而来的
li=['a','b','c','d','e'] li=('a','b','c','d','e') li='hello' i=0 while i < len(li): print(li[i]) i+=1
迭代器指的是迭代取值的工具,该工具的特点是可以不依赖于索引取值
为何要用迭代器
为了找出一种通用的&可以不依赖于索引的迭代取值方式
如何用迭代器
可迭代的对象:但凡内置有.__iter__方法的对象都称之为可迭代的对象
迭代器对象:既内置有__iter__方法,又内置有__next__方法
关于__iter__方法:
调用可迭代对象的__iter__会的到一个迭代器对象
调用迭代器对象的__iter__会的到迭代器本身
总结迭代器的优缺点
优点:
1. 提供了一种通用的&可以不依赖于索引的迭代取值方式
2. 同一时刻在内存中只有一个值,更加节省内存
缺点:
1. 取指定值不如索引灵活,并且迭代器是一次性的
2. 无法预知迭代器数据的个数
可迭代的对象: 序列str,list,tuple 字典dict 集合set与文件对象
迭代器对象: 文件对象
事实上iter函数与__iter__方法联系非常紧密,iter()是直接调用该对象的__iter__(),并把__iter__()的返回结果作为自己的返回值,故该用法常被称为“创建迭代器”。
dic={'x':1,'y':2,'z':3} iter_dic=dic.__iter__() #把字典转化成迭代器 print(iter_dic) res1=iter_dic.__next__() print(res1) res2=iter_dic.__next__() print(res2) res3=iter_dic.__next__() print(res3) res4=iter_dic.__next__() print(res4)
文件也是可迭代对象
iter_dic=open(r'F:\python学习代码\fb_user_name.txt',mode='rt',encoding='utf-8') while True: try: #try用来捕捉异常,使程序可以正常进行下去 print(iter_dic.__next__()) except StopIteration: break
for准确地说应该是迭代器循环,for循环的原理如下:
1. 先调用in后面那个值的__iter__方法,得到迭代器对象
2. 执行迭代器.__next__()方法得到一个返回值,然后赋值给一个变量k,运行循环体代码
3. 循环往复,直到迭代器取值完毕抛出异常然后捕捉异常自动结束循环
dic={'x':1,'y':2,'z':3} iter_dic=dic.__iter__() print(iter_dic) print(iter_dic.__iter__()) for k in dic: #iter_dic=dic.__iter__() print(k) with open(r'db.txt',mode='rt',encoding='utf-8') as f: for line in f: #iter_f=f.__iter__() print(line)
自定义迭代器
yield关键字:只能用在函数内
在函数内但凡包含有yield关键字,再去执行函数,就不会立刻运行函数体代码了
会得到一个返回值,该返回值成之为生成器对象,生成器本质就是迭代器
总结yield:
1. 提供一种自定义迭代器的解决方案
2. yield可用于返回值
yield VS return
相同点:都可以用于返回值
不同点:yield可以暂停函数,yield可以返回多次值,而return只能返回值一次值函数就立刻终止
def func(): print('=====>第一次') yield 1 print('=====>第二次 ') yield 2 print('=====>第三次') yield 3 print('=====>第四次')
def my_range(start,stop,step=1): while start < stop: yield start start+=step res=my_range(1,5,2) # 1 3 next(res) next(res) # print(next(res)) #StopIteration for item in res: print(item) ''' 1 3 ''' for item in my_range(1,5,2): print(item) ''' 1 3 '''
迭代期间不能修改被迭代的对象
在python中,for循环相当于一个迭代器(Iterator),在循环体中改变循环变量的值对循环次数是没有影响的。
迭代器在一个独立的线程中工作,并且拥有一个mutex互斥锁。迭代器被创建的时候,建立了一个内存索引表(单链表),这个索引表指向原来的对象,当原来的对象数量改变的时候,这个索引表的内容没有同步改变,所以当索引指针往下移动的时候,便找不到要迭代的对象,于是产生错误。就是说迭代器在工作的时候,是不允许被迭代的对象被改变的。
import time print("when we use 'for':",time.ctime()) for i in range(10): print('i:',i) for j in range(i+1,10): print('\tj=',j) if i+j<5: j=j+1 continue else: i=j #在这一步,从实际结果来看i的值并没有被修改,这一步完全没有执行 break print("over':",time.ctime()) ''' when we use 'for': Wed Nov 14 20:24:08 2018 i: 0 j= 1 j= 2 j= 3 j= 4 j= 5 i: 1 j= 2 j= 3 j= 4 i: 2 j= 3 i: 3 j= 4 i: 4 j= 5 i: 5 j= 6 i: 6 j= 7 i: 7 j= 8 i: 8 j= 9 i: 9 over': Wed Nov 14 20:24:08 2018 '''
从运行结果可以看出,i并没有变动
List、Set等是动态的、可变对象数量的数据结构,但是迭代器是单向不可变、只能顺序读取、不能逆序操作的数据结构,当迭代器指向的原始数据发生变化时,迭代器自己就迷失了方向。因此,我们可以改用while循环
import time print("\nwhen we use 'while':",time.ctime()) i=0 while i < 10: print('i:',i) j=i+1 while j < 10: print('\tj=',j) if i+j<5: j=j+1 continue else: i=j break i=i+1 print("over':",time.ctime()) ''' when we use 'while': Wed Nov 14 20:26:08 2018 i: 0 j= 1 j= 2 j= 3 j= 4 j= 5 i: 6 j= 7 i: 8 j= 9 over': Wed Nov 14 20:26:08 2018 '''
此时就得到了我们想要的效果
需要注意的是,while循环的运行效率比for循环低,所以会花费较多的时间,在数值非常大时才能体现出来
在for循环中直接更改列表中元素的值不会起作用
l = list(range(10)[::2]) print (l) for n in l: n = 0 print (l)
运行结果:
[0, 2, 4, 6, 8] [0, 2, 4, 6, 8]
l中的元素并没有被修改
在for循环中更改list值的方法
使用range
l = list(range(10)[::2]) print (l) for i in range(len(l)): l[i] = 0 print (l) '''运行结果 [0, 2, 4, 6, 8] [0, 0, 0, 0, 0] '''
使用enumerate
l = list(range(10)[::2]) print (l) for index,value in enumerate(l): l[index] = 0 print (l) '''运行结果 [0, 2, 4, 6, 8] [0, 0, 0, 0, 0] '''
生成器
由于生成器自动实现了迭代器协议,而迭代器协议对很多人来说,也是一个较为抽象的概念。所以,为了更好的理解生成器,我们需要简单的回顾一下迭代器协议的概念。迭代器协议是指:对象需要提供next方法,它要么返回迭代中的下一项,要么就引起一个Stoplteration异常,以终止迭代可迭代对象就是:实现了迭代器协议的对象协议是一种约定,可迭代对象实现迭代器协议,Python的内置工具(如for循环,sum,min,max函数等)使用迭代器协议访问对象。为什么在Python中,文件还可以使用for循环进行遍历呢?这是因为,在Python中,文件对象实现了迭代器协议,for循环并不知道它遍历的是一个文件对象,它只管使用迭代器协议访问对象即可。正是由于Python的文件对象实现了迭代器协议,我们才得以使用如此方便的方式访问文件Python使用生成器对延迟操作提供了支持。所谓延迟操作,是指在需要的时候才产生结果,而不是立即产生结果。这也是生成器的主要好处。
Python有两种不同的方式提供生成器:生成器函数:常规函数定义,但是,使用yield语句而不是return语句返回结果。yield语句一次返回一个结果,在每个结果中间,挂起函数的状态,以便下次重它离开的地方继续执行生成器表达式:类似于列表推导,但是,生成器返回按需产生结果的一个对象,而不是一次构建一个结果列表Python不但使用迭代器协议,让for循环变得更加通用。大部分内置函数,也是使用迭代器协议访问对象的。例如,sum函数是Python的内置函数,该函数使用迭代器协议访问对象,而生成器实现了迭代器协议,所以,我们可以直接这样计算一系列值的和:语法上和函数类似:生成器函数和常规函数几乎是一样的。它们都是使用def语句进行定义,差别在于,生成器使用yield语句返回一个值,而常规函数使用return语句返回一个值自动实现迭代器协议:对于生成器,Python会自动实现迭代器协议,以便应用到迭代背景中(如for循环,sum函数)。由于生成器自动实现了迭代器协议,所以,我们可以调用它的next方法,并且,在没有值可以返回的时候,生成器自动产生Stoplteration异常状态挂起:生成器使用yield语句返回一个值。yield语句挂起该生成器函数的状态,保留足够的信息,以便之后从它离开的地方继续执行使用生成器以后,代码行数更少。不使用生成器的时候,对于每次结果,我们首先看到的是result.append(index),其次,才是index。也就是说,我们每次看到的是一个列表的append操作,只是append的是我们想要的结果。使用生成器的时候,直接yield index,少了列表append操作的干扰,我们一眼就能够看出,代码是要返回index。这个例子充分说明了,合理使用生成器,能够有效提高代码可读性。只要大家完全接受了生成器的概念,理解了yield语句和return语句一样,也是返回一个值。那么,就能够理解为什么使用生成器比不使用生成器要好,能够理解使用生成器真的可以让代码变得清晰易懂。
import time def genrator_fun1(): a = 1 print('现在定义了a变量') yield a b = 2 print('现在又定义了b变量') yield b g1 = genrator_fun1() print('g1 : ',g1) #打印g1可以发现g1就是一个生成器 print('-'*20) #我是华丽的分割线 print(next(g1)) time.sleep(1) #sleep一秒看清执行过程 print(next(g1))
生成器有什么好处呢?就是不会一下子在内存中生成太多数据
import time def tail(filename): f = open(filename) f.seek(0, 2) #从文件末尾算起 while True: line = f.readline() # 读取文件中新的文本行 if not line: time.sleep(0.1) continue yield line tail_g = tail('tmp') for line in tail_g: print(line)
seed
def generator(): print(123) content = yield 1 print('=======',content) print(456) yield g = generator() ret = g.__next__() print('***',ret) ret = g.send('hello') #send的效果和next一样 print('***',ret) ''' 123 *** 1 ======= hello 456 *** None '''
send 获取下一个值的效果和next基本一致只是在获取下一个值的时候,给上一yield的位置传递一个数据
使用send的注意事项
第一次使用生成器的时候 是用next获取下一个值
最后一个yield不能接受外部的值
计算移动平均值
def averager(): total = 0.0 count = 0 average = None while True: term = yield average total += term count += 1 average = total/count
计算移动平均值(2)_预激协程的装饰器
def init(func): #在调用被装饰生成器函数的时候首先用next激活生成器 def inner(*args,**kwargs): g = func(*args,**kwargs) next(g) return g return inner @init def averager(): total = 0.0 count = 0 average = None while True: term = yield average total += term count += 1 average = total/count g_avg = averager() # next(g_avg) 在装饰器中执行了next方法 print(g_avg.send(10)) print(g_avg.send(30)) print(g_avg.send(5))
这两个例子都出自于流畅的pathon
yield from
def gen1(): for c in 'AB': yield c for i in range(3): yield i print(list(gen1())) def gen2(): yield from 'AB' yield from range(3) print(list(gen2())) ''' ['A', 'B', 0, 1, 2] ['A', 'B', 0, 1, 2] '''
生成器与列表解析的区别
生成器表达式来源于迭代和列表解析的组合,生成器和列表解析类似,但是它使用尖括号而不是方括号 # 列表解析生成列表 [ x ** 3 for x in range(5)] [0, 1, 8, 27, 64] # 生成器表达式 (x ** 3 for x in range(5)) <generator object <genexpr> at 0x000000000315F678> # 两者之间转换 list(x ** 3 for x in range(5)) [0, 1, 8, 27, 64]
1.把列表解析的[]换成()得到的就是生成器表达式
2.列表解析与生成器表达式都是一种便利的编程方式,只不过生成器表达式更节省内存
3.Python不但使用迭代器协议,让for循环变得更加通用。大部分内置函数,也是使用迭代器协议访问对象的。例如, sum函数是Python的内置函数,该函数使用迭代器协议访问对象,而生成器实现了迭代器协议,所以,我们可以直接这样计算一系列值的和:
sum(x ** 2 for x in range(4))
而不用多此一举的先构造一个列表:
sum([x ** 2 for x in range(4)])
三元表达式(实际上是类似三元表达式的if表达式)
def max2(x,y): if x > y: return x else: return y x=10 y=20 res='条件成立的值' if x > y else '条件不成立的值' print(res) 也可以用简单的公式,如下, a = 1 b = 2 h = '' h = a-b if a>b else a+b print(h) s="ok" s2="tzc" v="yes" if s=="ok" else ("yes" if s2=="tzc" else "no") print(v) #yes
列表生成式
语法 [要放入列表的数据 简单的表达式1 表达式2]
#列表生成式是快速生成一个列表的一些公式 numbers = [] for x in range(0,101): numbers.append(x) print(numbers) #x for x in range(0,101) for循环遍历出来的值,放入列表中 numbers =[x for x in range(0,101)] print(numbers)
自定义输出的格式
l=[] for i in range(1,11): if i > 4: res='egg%s' %i l.append(res) print(l) l=['egg%s' %i for i in range(1,11) if i > 4] print(l) ''' ['egg5', 'egg6', 'egg7', 'egg8', 'egg9', 'egg10'] ['egg5', 'egg6', 'egg7', 'egg8', 'egg9', 'egg10'] '''
筛选出需要的,也就是加上条件
names=['egon','lxx','yyx','cw','alex','wxx'] l=[] for name in names: if name != 'egon': res='%s_DSB' %name l.append(res) print(l) l=['%s_DSB' %name for name in names if name != 'egon'] print(l)
#普通双重for循环 list7 =[] for x in range(0,10): for y in range(10,20): s = x*y list7.append(s) print(list7) #支持双重for循环 list3 = [x*y for x in range(0,10) for y in range(10,20)]print(list3)
列表生成器和列表生成式的区别 list6 = [x for x in range(10)] print(list6) #generator列表生成器#生成的是一个对象,不会把数据直接创建出来,当for遍历的时候,生成器对象会调用next()函数,获取下一个要生成的数据 generator = (x for x in range(10)) #生成式对象可以调用next()函数获取下一个要生成的数字,如果next()函数没有获取到下一个数据,会抛出异常StopIteration ,程序出错 #生成式对象可以使用for遍历,使用next()不停的获取下一个数据,如果没有下一个数据循环结束 for x in generator: print(x)
字典生成式
items=[('name','egon'),('age',18),('sex','male')] dic={} for k,v in items: dic[k]=v print(dic) res={k:v for k,v in items if k != 'sex'} print(res) res={i for i in 'hello'} print(res) ''' {'name': 'egon', 'age': 18, 'sex': 'male'} {'name': 'egon', 'age': 18} {'e', 'h', 'l', 'o'} '''