【Python 装饰器&迭代器&生成器&匿名函数 06】
1、函数名就相当于一个变量,变量怎么用函数名就怎么用
#并不是只有函数名才能调用一个函数 # 只要是函数的地址对应的变量都可以通过()调用函数 def a(): print('I me fine') a() print(a) #打印a的内存地址:<function a at 0x107708040> b=a #把a赋值给b print(b) #打印b的内存地址:<function a at 0x107708040> l=[a] #把a放到列表内 print(l[0]) #<function a at 0x10ea84040> l[0]() #调用a函数
import time # 函数的名字也可以做参数 def timmer(func): start_time = time.time() func end_time=time.time() print(end_time-start_time) def clac_1(): s=0 for i in range(10000): s +=i def clac_2(): s=0 for i in range(10000): s +=i timmer(clac_1()) timmer(clac_2())
2、闭包:如果一个内部函数引用了外部函数的变量,那么这个内部函数就是一个闭包函数
def wahaha(): name = 'alex' def a(): '''一旦内层函数引用了外层函数的变量,内层函数就是一个闭包函数''' print('i me fine',name) return a b = wahaha() #调用wahaha函数,返回是a函数,然后把内存地址赋值给b b()
|-- 闭包有什么用?
A写了一个程序,B,C,D不管是什么类型的函数,都可以调用A,那么A程序的实现方式就是闭包
#案例:timemer函数可以计算其他函数的运行时间,clac_1(),clac_2(m,n)是运行函数
#闭包 --> 应用1:计算时间 import time # 计算时间的函数 def timemer(funtime): def inner(*args,**kwargs): start_time = time.time() ret = funtime(*args,**kwargs) end_time = time.time() print(end_time-start_time) return ret return inner # 计算时间的函数clac_1 @timemer def clac_1(): s=0 for i in range(1000000): s +=i # print('clac_1-->',s) return s # 计算时间的函数clac_1 @timemer def clac_2(m,n): print(m,n) s = 0 for i in range(1000000): s += i # print('sclac_2-->', s) return s # func = timemer(clac_1) #调用timemer(clac_1)返回值是inner函数,然后赋值给func, # func() # 调用func()就相当于调用的是inner()函数 clac_1() '''如果我要计算clac_1()函数,但是函数里面有两个参数,调用timemer(clac_1)就不会成,原因是调用 timemer(clac_1)其实是调用的inner()函数的内存地址,那怎么解决''' '''解决办法: 1、在inner函数内加参数*args,这样就可以灵活存不用数量的参数 ''' # func2 = timemer(clac_2) # func2(10000,20) clac_2(10000,20) '''如果func2(a=10000,20)这么传,也是会报错的,如何解决:inner()函数内还需要加上**kwargs''' # func2(m=99999,n=99) # func2(88888,n=88) clac_2(m=99999,n=99) clac_2(88888,n=88) '''如果clac_1和clac_2不打印s,而是直接返回s,那么在调用timemer(funtime)的时候我们也需要把funtime返回,如果不返回结果就会是none'''
通过上面的代码,其实就可以发现,timemer函数是一个公共的函数,任何函数都可以调用它,这样的函数其实就是一个装饰器
====> 上面的代码其实有个大bug,我们本应该是一调用 clac_1, clac_2函数就可以计算耗时,那么如何实现呢?只需要在函数方法之前加@timemer即可,他的作用相当于
func = timemer(clac_1) #调用timemer(clac_1)返回值是inner函数,然后赋值给func,func=inner内存地址
一、装饰器
1、什么情况下用装饰器
在已经写好发版程序的基础上,需要对一个函数执行前后增加功能的时候
开放封闭原则:
开发:对扩展是开放的
封闭:对修改是封闭的
有时候也会写好一些装饰器
2、装饰器的固定格式
def 装饰器名字(func): def inner(*args,**kwargs): '''在执行被装饰的函数之前要做的事儿''' '''比如:判断是否登录''' ret = func(*args,**kwargs) '''在执行被装饰的函数之后要做的事儿''' '''比如:写log'''
return ret return inner
#案例:利用装饰器把登录和注册的日志写到日志文件operrate.log内
# 利用装饰器把登录和注册的日志写到日志文件operrate.log内 import time from functools import wraps def wrapper(func): @wraps(func) def inner(*args,**kwargs): ret=func(*args,**kwargs) t = time.strftime('%Y-%m-%d %H:%M:%S') # print('%s在[%s]执行了%s函数'%(args[0],t,func.__name__)) write_log('%s在[%s]执行了%s函数'%(args[0],t,func.__name__)) return ret return inner def write_log(context): f = open(r'operrate.log',mode='a',) f.write(context) f.write('\n') f.close() @wrapper def login(name): print('%s登录了'%name) login('alex') @wrapper def register(name): print('%s注册了'%name) register('alex') print(register.__name__) # inner -->这里调用register函数的名字,但是输出的是inner,如何才能伪装的好一些输出register函数? # 解决办法:引入wraps装饰器,然后在inner前加上@wraps(func) 这样就会输出注册名
3、一个函数可以被多个装饰器装饰
# 三个装饰器 def warpper1(fun): def inner1(*args,**kwargs): print('before in inner1') ret = fun(*args,**kwargs) print('after in inner1') return ret return inner1 def warpper2(fun): def inner2(*args,**kwargs): print('before in inner2') ret = fun(*args,**kwargs) print('after in inner2') return ret return inner2 def warpper3(fun): def inner3(*args,**kwargs): print('before in inner3') ret = fun(*args,**kwargs) print('after in inner3') return ret return inner3 @warpper1 @warpper2 @warpper3 def wahaha(): print('wahaha') wahaha() 执行结果: before in inner1 before in inner2 before in inner3 wahaha after in inner3 after in inner2 after in inner1 #从执行结果发现:挨着函数越近的装饰器,执行前和执行后都紧紧挨着执行
各装饰器放位置的关系:
@timmer装饰器 计算被装饰函数的时间,永远紧贴被装饰的函数
@login装饰器 检测被装饰的函数有没有登录,没有登录要先登录才能使用这个函数
@logging装饰器,可以放到任意位置
# 案例:利用装饰器把登录的日志写到login.log文件内,注册的日志写到register.log文件内
import time def log(filename): def wrapper(func): def inner(*args,**kwargs): ret = func(*args,**kwargs) t = time.strftime('%Y-%m-%d %H:%M:%S') write_file('%s在[%s]执行了%s函数'%(args[0],t,func.__name__),filename) return ret return inner return wrapper def write_file(context,filename): with open(filename,mode='a') as f: f.write(context) f.close() @log('login.log') def login(name): print('%s登录了'%name) @log('register.log') def register(name): print('%s注册了'%name) login('alex') register('alex')
较比上一个案例,优化的地方在于,把之前wrapper函数封装到了log的函数内,然后log函数的参数filename和write_file函数的参数形成闭包
但是log()函数就成了装饰器了
--------->这个就是带参数的装饰器,其实就是在没带参数装饰器的外面套一层
二、迭代器(iterator)
1、特点:
- 一个一个的取值,而不是一次性的把所有数据都取出来
- 只能按顺序取
- 迭代器中的数据,不取不创建,一个迭代器中的数据只能从头到尾取一次
- 所有能被for循环的类型,至少是一个可迭代类型
可迭代协议:如果一个数据类型中有iter方法,那么这个数据类型就是可迭代类型
迭代器协议:如果一个数据类型中有iter和next方法,那么这个数据类型就是一个迭代器类型
2、生成器(generator) 生成器的本质就是一个迭代器
生成器函数:没有return,只有yield
#简单案例,生成100w件衣服,说明生成器的作用 def make_cloth_simple(n): '''生成器函数,没有return,只有yield''' print('第一件衣服') yield 1 print('第二件衣服') yield 2 print('第三件衣服') yield 3 ret=make_cloth_simple(1000000) print(ret) #<generator object make_cloth_simple at 0x10f83c660> generator生成器 print(ret.__next__()) #第一件衣服,1 print(ret.__next__()) #第二件衣服,2 print(ret.__next__()) #第三件衣服,3
make_cloth_simple(1000000)传了100w,但是ret.__next__()也是一件一件的取,所以生成器本质上就是一个迭代器
yeild相当于一个暂停键,暂停了就返回一个值,区别于return,return是遇到就返回然后结束
#案例:一次性取100w件衣服和100w件衣服先取20件,然后在取10件,什么时间想取在取的区别
# 一次性取10w件衣服 def make_cloth_simple(n): for i in range(n): print('第%s件衣服'%i) # make_cloth_simple(100000) #100w件衣服,先取20件,有需要在取10件... def make_cloth(n): for i in range(n): yield '第%s件衣服'%i ret=make_cloth(100000) for n in range(20): print(ret.__next__()) print('=='*20) for n in range(10): print(ret.__next__()) ''' 生成器就相当于,我先签订了10w件衣服的订单合同,等什么时候需要做衣服的时候就做20件,等下次在需要的时候在做10件 '''
对比两者:利用生成器的一个优势就是节省内存空间,不断地从一个容器或者规则内不断的取数据,完成迭代
# 案例:A程序不断的往test文件内写数据,B程序只要监听到test文件内有数据,就在终端上打印出A程序写入的数据
写数据程序
# 这个程序不断的往test文件内写数据 def write_context(): while True: context = input('>>>>>') f = open(r'test.txt',mode='a',encoding='UTF-8') f.write(context) f.write('\n') f.close() write_context()
监听程序 -->在自动化中应用于监听到错误日志,直接写到日志文件内
# 只要监听到test文件内有数据,就把数据打印到屏幕上 def listen(): f = open(r'test.txt','r',encoding='utf-8') while True: context=f.readline().strip() # if context: #如果context存在的意思 # print(context) if 'error' in context: #如果写入的内容包含error信息,那么就暂停把context打印出来-->这样做的可以写一个监听日志的程序 yield context f.close() for context in listen(): print(context)
一看到yeild就知道这是一个生成器
#应用在真实工作中的案例:
--> userinfo 文件存放:用户名和密码
--> 程序文件:get_user函数获取用户名和密码,login函数根据get_user获取的用户名和密码进行登录判断,register函数根据get_user函数判断如果内存在就提示注册重复,不存在就注册成功
#读去userinfo的用户名和密码 def get_user(filename): lst = [] with open(filename,'r',encoding='utf-8') as f: for line in f: user,pwd = line.strip().split('|') lst.append((user,pwd)) # print(lst) return lst # get_user('userinfo') #[('alex', 'www'), ('wusir', '666')] '''上面的这种写法,可以把userinfo文件内的用户名和密码取出来,但是如果 userinfo内的数据是千万级别的,一次性取出来的话效率会非常低,所以最好用生成器来完成(用多少去多少)''' def get_user_generator(filename): with open(filename,'r',encoding='utf-8') as f: for line in f: user,pwd=line.strip().split('|') yield user,pwd f.close() # get_user_generator('userinfo') def set_user_generator(filename,username,passwd): with open(filename,'a',encoding='utf-8') as f: f.write('\n') f.write(username) f.write('|') f.write(passwd) f.close() # login函数 def login(): username = input('请输入用户名:') passwd = input('请输入密码:') for user,pwd in get_user_generator('userinfo'): if user==username and pwd == passwd: print('登录成功') break else: print('登录失败') login() # register函数 def register(): username = input('请输入用户名:') passwd = input('请输入密码:') for user,pwd in get_user_generator('userinfo'): if username == user or pwd ==passwd : username = input('用户名已存在!') break else: set_user_generator('userinfo', username, passwd) print('注册成功!') register()
#生成器理解题
def func(): for i in range(5): yield '%s个数'%i g = func() for i in g: print(i) for j in g: print(j) # 这种情况只会打印一次,原因生成器也是迭代器,而迭代器的特点是一个迭代器内的数据只能从头到尾取一次,就相当于只签一次合同 for k in func(): print(k) for o in func(): print(o) #这种情况就会打印两次,因为循环了两个生成器,就相当于签了一次合同 g1=g2=func() for k in g1: print(k) for o in g2: print(o) # 这种情况也只会打印一次,只循环了一个生成器
三、各种推导式
1、列表推导式
在一行内完成一个新列表的组件
# 让l列表内的数据乘以2,输出一个新列表l2 l=[1,2,3,4,5] l2=[i*2 for i in l] #循环l列表内的i,然后i*2形成一个新的列表 print(l2) #[2, 4, 6, 8, 10]
# 30以内所有能被3整除的数 l = [i for i in range(30) if i%3==0] print(l) #30以内所有能被3整除的数的平方 l2 = [i*i for i in range(30) if i%3==0] print(l2)
# 找到嵌套列表中名字含有两个e的所有名字 name = [['tom','alese','kkdee'],['rtyui','rtyuiyee','oooooppp']] # # 常规写法 ls=[] ''' 思路:先循环name列表,lst是每一个单独的列表(['tom','alese','kkdee']) 然后在循环lst去除列表内的值i('tom','alese','kkdee') 然后i.count('e')==2去判断i内的值包含2个e的,i.count('e') 是python中计算某一个值的数量 ''' for lst in name: for i in lst: if i.count('e')==2: ls.append(i) print(ls) # 列表推导式写法 l2=[i for lst in name for i in lst if i.count('e')==2] print(l2)
比较简单的可以用列表推导式,复杂的就用常规的方法写就可以了
2、字典推导式 3、集合推导式 ===》这两个推导式只了解即可
4、生成器表达式
列表推导式的[]换成()就变成了生成器稳定式
区别:列表推导式内的元素,只要取了就会一次性的把列表中的元素放到内存中
生成器表达式,只有在取的时候才会把元素放到内存中
#列表推导式 lst = [i*2 for i in range(5)] print(lst) #[0, 2, 4, 6, 8] #生成器表达式 gen = (i*2 for i in range(5)) print(gen.__next__()) #0 print(gen) #<generator object <genexpr> at 0x101982660> for i in gen: print(i) #2, 4, 6, 8 list(gen) # null '''从上面两则执行的结果可以看出: 1、lst列表一次性就把所有数据都放到内存中,展示出来 2、gen在__next__取了第一个以后,在for循环取,第一个因为取完了所以就取不到了 3、把gen转换成list,取的值为空,因为生成器之前都取完了,所以后面在取就没有了
----》标黄的内容是迭代器取值的三种方式 '''
四、三元运算符
a=10 b=15 # max_num=0 max_um = a if a> b else b print(max_um)
五、匿名函数
lambda修饰的函数就叫匿名函数
#lambda表达式,比较a,b两个值的大小,求比较大的值 compare=lambda a,b: a if a>b else b print(compare(5,2))