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()
posted @ 2019-07-13 23:01  www.pu  Views(235)  Comments(0Edit  收藏  举报