06 闭包函数 装饰器
一、闭包函数
1.闭:定义在函数内部的函数
2.包:内部函数引用了外部函数作用域的名字(内部函数将自己函数名返回给外部函数,外部函数加括号就可以调用内部函数)
1.1 不包
小爬虫
爬虫的本质就是爬取页面的html代码
从中获取到你想要的数据(url链接地址)
有了链接之后 你就可以顺着这个链接将所有的页面资源全部爬取下来
# 第一个直接给函数传参 import requests def my_get(url): response = requests.get(url) if response.status_code == 200: print(len(response.text)) my_get('https://www.baidu.com') my_get('https://www.baidu.com') my_get('https://www.baidu.com')#每一次都要输入网址,麻烦不?
1.2.1 闭包——简单包
# 第二种给函数传参的方式 闭包 import requests def outter(): url = 'https://www.baidu.com' # 实现初级闭包,但是这样就把网站定死了,只能登陆一个网站,不能改变,是不是很没用? def my_get(): response = requests.get(url) if response.status_code == 200: print(len(response.text)) return my_get res = outter() # 相当于my_get res() #2433 res() #2433
1.2.2 闭包——完美包
import requests def outter(url): # 网址通过参数传入,就可以在外部传入不同网址 # url = 'https://www.jd.com' def my_get(): response = requests.get(url) if response.status_code == 200: print(len(response.text)) return my_get my_jd = outter('https://www.jd.com') # 2443 相当于my_get,传入一次网址之后可以不传 my_jd() #92680 my_jd() #92680 my_baidu = outter('https://www.baidu.com') my_baidu() #2443 my_baidu() #2443
二、装饰器
1.定义:给被装饰对象添加新的功能的一个工具 2. 开放封闭原则: 开放:对扩展开放 封闭:对修改封闭 3.装饰器(可调用对象)必须遵循的两个原则: 1.不改变被装饰对象源代码 2.不改变被装饰对象 {必须为可调用对象callable(加了括号就能调用的),比如函数和类} 调用方式 4.补充知识 import time print(time.time()) 1562812014.731474 时间戳 当前时间距离1970-1-1 00:00:00相差的秒数 1970-1-1 00:00:00是Unix诞生元年 time.sleep(3) # 让cpu睡三秒 让你的程序暂停三秒
2.1 简单装饰器 有无参数不兼容
#题目:统计index函数执行的时间 import time def index(): time.sleep(3) print('澳门最大线上赌场开业啦 性感tank在线发牌!') #第一步;常规方法 start = time.time() index() end = time.time() print('index run time:%s'%(end-start)) #功能虽然实现了,但是只能给一个函数用,如果有一百个函数需要计时怎么办?? #第二步:通过调用函数实现可以给多个函数使用问题 def get_time(): start = time.time() index() end = time.time() print('index run time:%s'%(end-start)) get_time() # (加一层函数)这样定义一个函数虽然也实现了功能,但是不仅改变了调用方式而且只能给一个函数用 def get_time(func): #此处func=index函数名 start = time.time() func() #相当于index()直接启动index函数 end = time.time() print('index run time:%s'%(end-start)) get_time(index) # (在加一层函数解决了函数单一使用问题)这样虽然可以给多个函数使用了,但是改变了原本的调用方式 #第三步:解决改变原来调用方式的问题,完成简单装饰器(不兼容版本) #我们再加一层函数解决调用方式问题 def outter(func): # func = 最原始的index函数的内存地址 def get_time(): start = time.time() func() # 当get_time激活后从上至下一句一句执行,到这里偶遇index函数,激活index函数 end = time.time() print('index run time:%s'%(end-start)) return get_time # res=outter(index) # res就是一个函数名,想写什么写什么,改为index就神奇了,所以有了下面的进化语句 #res() index = outter(index) # outter(最原始的index函数内存地址),outter激活,执行内部代码只定义不执行一个get_time函数 index() # index指向get_time函数的内存地址,加括号启动get_time函数,遇到func=index函数,先执行完index函数再往下走print
#看起来和以前的调用方式一样,其实本质不同,这个index只是一个普通的变量名
2.2 升级版装饰器 兼容问题 + 返回值函数名查真伪
1.解决装饰器可以装饰无参有参任意函数问题 # #题目:统计index函数执行的时间 import time def index(): time.sleep(3) print('澳门最大线上赌场开业啦 性感tank在线发牌!') return 'index' print(index()) def get_name(name): time.sleep(2) print('我是%s'%name) return 'get_name'
#以下为有参无参都兼容装饰器 def outter(func): def get_time(*args,**kwargs): #*将接受到的参数打包成元组,**将接收的参数打包成字典 start = time.time() func(*args,**kwargs) # *将上面传过来元组解压成位置参数,**将字典解压为关键字参数,实现外部传来数据的还原 end = time.time() print('index run time:%s'%(end-start)) return get_time index = outter(index) index() get_name = outter(get_name) get_name('jason') #当给index函数使用后,如果get_name函数想用,就要在get_time()、func()后加参数(name),完事后index之类的无参函数又想用,就不得不再去掉参数,
是不是很麻烦?因此我们在get_time()后、func()后用*和**传参就解决了,管你有没有参数照单接收
2.解决返回值漏洞 上面的装饰器解决了有参无参函数使用不兼容问题,但是假如用户鸡贼,非要输出get_time()看函数返回值,就会发现函数真实面目 并不是'get_time'而是get_time()
的返回值也就是None,那么要想真正的以假乱真,我们需要给get_time()一个返回值让这个返回值为'get_time' def outter(func): def get_time(*args, **kwargs): start = time.time() res = func(*args, **kwargs) #此处的func()本质就是get_name(),我们把他接收然后再返回给get_time()就可以了 end = time.time() print('func run time:%s'%(end-start)) return res return get_time get_name = outter(get_name) res = get_name('jason') print(res)
3.装饰器修复 解决函数名漏洞
如果不调用修复模块进行修复,用户查看函数名或者输出函数名字符串就可以发现函数被掉包了,是inner而不是index,修复后就查不出来了
from functools import wraps
def outter(func):
@wraps(func) # 装饰器修复技术
def inner(*args,**kwargs):
"""
我是inner函数
"""
print('执行被装饰函数之前 你可以执行的操作')
res = func(*args,**kwargs)
print('执行被装饰函数之后 你可以执行的操作')
return res
return inner
@outter # index = outter(最原始的index内存地址)
def index():
"""
我是index函数
"""
pass
print(index)
print(help(index)) # 查看函数的注释
print(index.__name__) # 查看函数名字符串形式
index()
2.3 装饰器语法糖
1.语法糖将被装饰函数的函数名当做装饰器函数的参数,然后打包丢冒牌的的那个变量名 2.语法糖在书写的时候应该与被装饰对象紧紧挨着,两者之间不要有空格 import time def outter(func): def get_time(*args, **kwargs): start = time.time() res = func(*args, **kwargs) end = time.time() print('func run time:%s'%(end-start)) return res return get_time @outter # 本质就是index = outter(index) 将函数名index作为outter()函数的参数赋值给变量名index这个冒牌货 def index(): time.sleep(3) print('澳门最大线上赌场开业啦 性感tank在线发牌!') return 'index' @outter #本质就是get_name = outter(get_name) def get_name(name): time.sleep(2) print('我是%s'%name) return 'get_name' index() # 直接调用就可以了,不用装饰一个函数写一个index = outter(index)了,骗人的事交给python去帮着干 get_name('egon')
2.4 装饰器模板
#无参装饰器
from functools import wraps
def outter(func):
@wraps(func) def inner(*args,**kwargs): print('执行被装饰函数之前 你可以做的操作') res = func(*args,**kwargs) print('执行被装饰函数之后 你可以做的操作') return res return inner
#有参装饰器(给装饰器传参)
from functools import wraps
def outter2(*a,**b)
def outter(func):
@wraps(func) def inner(*args,**kwargs): print('执行被装饰函数之前 你可以做的操作') res = func(*args,**kwargs) print('执行被装饰函数之后 你可以做的操作') return res return inner
return outter
2.5.1 异常装饰器
# 异常装饰器,处理函数的异常 def entry_wrap(func): """ 如异常会截图,并返回: {'error': type, 'msg': error_message, 'error_img': screenshot_path} 如果正常,则返回: {'content': function_return_value } """ def wrapper(*args, **kwargs): try: content = func(*args, **kwargs) # 被装饰任务的返回值 result = {'content': content} # 正常情况下这样返回 except TypeError as e: # 指名道姓捕捉,try中代码执行遇到TypeError后走这下面 result = {'error': 'TypeError', 'msg': e} except BaseException as e: # 其他所有异常走这下面 result = {'error': '其他未知错误', 'msg': e} if 'error' in result: try: # 这里可以做一系列功能,如对错误的页面截图,返回结果中增加截图路径 result['error_img'] = 'screenshot_path' except: pass return result return wrapper @entry_wrap # 函数一旦被装饰,返回结果就由装饰器的返回值决定,不再是函数自己原汁原味的返回结果 def test(name): try: # 正常情况走这里 name += 1 return {'name':1,'age':2} except: # 捕捉所有异常 name += [] name.split('|') return {'error': 'code', 'msg': 'error_message',} finally: # 出不出错最后都一定会走这里,这里面可以进行文件资源关闭操作 pass no_error = test(1) print(no_error) # 正常输出 error_1 = test('a|b') print(error_1) # TypeError error_2 = test([1,2]) print(error_2) # BaseException -> AttributeError """ {'content': {'name': 1, 'age': 2}} {'error': 'TypeError', 'msg': TypeError('can only concatenate str (not "list") to str'), 'error_img': 'screenshot_path'} {'error': '其他未知错误', 'msg': AttributeError("'list' object has no attribute 'split'"), 'error_img': 'screenshot_path'} """
2.5.2 认证装饰器
认证装饰器 需求1:执行函数index之前必须先输入用户名和密码,正确之后才能执行index,否则提示用户输入错误 结束程序 需求2:验证成功后当前用户再次使用装饰器时自动放过,不再重复验证密码 user_dict = {'is_login':None} def outter(func): def input_name(*args,**kwargs): if user_dict['is_login']: res = func(*args, **kwargs) else: name = input('请输入名字:').strip() pwd = input('请输入你的密码:').strip() if name == '123' and pwd == '123': user_dict['is_login'] = True res = func(*args,**kwargs) else: print('错误!') return return res return input_name # index = outter(index) @outter def index(name): print('%s我爱死你了!'%name ) index('www.pu') index('www.pu')
2.6 多层装饰器
装饰器在装饰的时候 顺序从下往上
装饰器在执行的时候 顺序从上往下
# 装饰器在装饰的时候 顺序从下往上 # 装饰器在执行的时候 顺序从上往下 import time user_dic = {'is_login':None} def login_auth(func): def inner(*args,**kwargs): if user_dic['is_login']: res = func(*args, **kwargs) return res else: username = input('please input your username>>>:').strip() password = input('please input your password>>>:').strip() if username == '123' and password == '123': user_dic['is_login'] = True res = func(*args,**kwargs) return res else: print('username or password error') return inner def outter(func): def get_time(*args, **kwargs): start = time.time() res = func(*args, **kwargs) end = time.time() print('func run time:%s'%(end-start)) return res return get_time @login_auth #index=inner=login_auth(get_time) login_auth(func)变成login_auth(get_time)传入参数启动装饰器,然后login_auth函数 # 会返回一个值inner=login_auth(get_time),最后再把inner偷换成变量名index开始以假乱真开始启动inner函数 @outter #get_time=outter(index) outter(func)变成outter(index)传入参数启动装饰器,然后outter函数会返回一个值get_time=outter(index) def index(): time.sleep(2) print('index') return index index()
2.7 三层装饰器 加深理解
def outter1(func1): print('加载了outter1') def wrapper1(*args,**kwargs): print('执行了wrapper1') res1=func1(*args,**kwargs) return res1 return wrapper1 def outter2(func2): print('加载了outter2') def wrapper2(*args,**kwargs): print('执行了wrapper2') res2=func2(*args,**kwargs) return res2 return wrapper2 def outter3(func3): print('加载了outter3') def wrapper3(*args,**kwargs): print('执行了wrapper3') res3=func3(*args,**kwargs) return res3 return wrapper3 @outter1 # index = outter1(wapper2) @outter2 # wrapper2 = outter2(wrapper3) @outter3 # wrapper3 = outter3(最原始的index函数内存地址) def index(): print('from index') """ 加载了outter3 加载了outter2 加载了outter1 执行了wrapper1 执行了wrapper2 执行了wrapper3 from index """ index()
2.8 有参装饰器实例
#让用户选择登录数据来源,说从文件来就找文件,指定数据库就找数据库,怎么办?在装饰器外面再加一层函数给装饰器传参数! import time user_dic = {'is_login':None} def outter(func): def get_time(*args, **kwargs): start = time.time() res = func(*args, **kwargs) end = time.time() print('func run time:%s'%(end-start)) return res return get_time def login_auth2(data_source,x,t): #这里可以传给装饰器好多好多参数,用的时候来这一层找就好了 # data_source = 'file' #转化为参数,把函数写活 def login_auth(func): def inner(*args,**kwargs): # 这里的参数是跟被装饰函数的参数一一对应,不能用来传递多与参数,想传参就需要再外加一层函数 if user_dic['is_login']: res = func(*args, **kwargs) return res else: if data_source == 'file': username = input('please input your username>>>:').strip() password = input('please input your password>>>:').strip() if username == '123' and password == '123': user_dic['is_login'] = True res = func(*args,**kwargs) return res else: print('username or password error') elif data_source == 'MySQL': #简单演示不要管什么意思 print('from MySQL') elif data_source == 'ldap': print('ldap') else: print('暂无该数据来源') return inner return login_auth # res = login_auth2('MySQL') #这样太麻烦,直接在语法糖后加参数就行 # @res @login_auth2('file',1,2) # login_auth2本来就是func(),再加一个('file',1,2),会先把这些参数传入login_auth2 @outter def index(): time.sleep(1) print('index') return 'index' index()