zoukankan      html  css  js  c++  java
  • 闭包函数与装饰器

    闭包函数与装饰器

    一、什么是闭包

    闭包:闭是封闭(函数内部函数),包是包含(函数内部函数对外部作用域而非全局作用域的变量的引用)。闭包指的是:函数内部函数对外部作用域的引用。

    x = 111  # 全局作用域的变量
    def outer():
        y = 222  # 局部作用域的变量
        def inner():
            print(x,y)  # 引用了全局和局部作用域的变量
        return inner
    

    二、给函数体传值的的两种方式

    1、传参

    def save_file(filename,content):  # 通过参数给函数体代码传值
        with open(filename,'a',encoding='utf-8') as fw:
            fw.write(content)
        return 
    

    2、闭包

    def deco():
        x = 1   # x的值写死了,无法修改 
        y = 10  # y的值写死了,无法修改 
        def my_max():
            if x > y:  # 函数内部函数x对外部作用域x=1的引用,就是闭包
                return x
            return y
        return my_max
    
    res = deco()
    print(res())
    

    # 闭包函数进阶
    def deco(x,y):  # x,y的值在调用时可以改变。
        def my_max():
            if x > y:
                return x
            return y
        return my_max
    res = deco(4,9)  # 优点一次传参,多次调用。
    print(res())
    print(res())
    

    三、闭包函数的应用

    闭包的意义:返回的函数对象,不仅仅是一个函数对象,在该函数外还包裹了一层作用域,这使得,该函数无论在何处调用,先使用自己外部包裹的作用域。

    闭包函数,可以是一次传参,多次调用。

    应用领域:延迟计算(原来我们是传参,现在我们是包起来)、爬虫领域。

    # 使用函数传参实现
    import requests
    url = 'https://www.baidu.com'
    url2 = '....'
    def my_get(url):
        response = requests.get(url)
        print(response.text)
    
    my_get(url)  # 需要时,每次都要定义网址。如果需要爬取的网址比较多,会特别麻烦
    my_get(url) 
    my_get(url)
    
    # 使用闭包函数实现
    def deco(url):
        def my_get():
            response = requests.get(url)
            print(response.text)
        return my_get
    
    my_jd = deco('http://www.jd.com')  # 一次定义,多次调用调用
    my_jd()  # 多次调用闭包函数
    my_jd()
    my_jd()
    my_baidu = deco('https://www.baidu.com')
    my_baidu()
    my_baidu()
    my_baidu()
    my_baidu()
    

    装饰器

    什么是装饰器?

    器就是一个工具,而程序中的函数就是具备某一功能的工具,所以装饰器就是给被装饰对象添加新的功能。因此定义装饰器就是定义一个函数,只不过该函数的功能是用来为其他函数添加新的功能。

    需要注意的是:

    • 装饰器本身其实是任意可调用的对象
    • 被装饰的对象也是任意可调用的对象

    为什么要用装饰器?

    如果我们上线了一个项目,我们需要修改某一个方法,但是我们不想修改方法的使用方法,这个时候可以使用装饰器。因为软件的维护必须遵循开封封闭原则,即软件一旦上线运行后,软件的维护对修改源代码是封闭的,对扩展功能值的是开放的。

    装饰器的实现必须遵循两大原则:

    ​ 1.不修改被装饰对象的源代码

    ​ 2.不修改被装饰对象的调用方式

    怎么用装饰器?

    # 提前了解知识
    # 导入时间模块
    import time
    print(time.time())  # 时间戳 当前时间距离1970-1-1 00:00:00相差的秒数
    # 1970-1-1 00:00:00是Unix诞生元年
    time.sleep(1)  # 睡眠3秒,程序暂停3秒
    print('FBI warning!')
    

    要实现统计index函数执行的时间

    # 源代码
    import time
    def index():
        time.sleep(3)
        print('澳门最大赌场上线了 性感MM在线发牌')
    
    # 通过修改源代码实现,实现统计index函数执行的时间
    import time
    def index():
        time.sleep(3)
        print('澳门最大赌场上线了 性感MM在线发牌')
    start = time.time()
    index()
    end = time.time()
    print(f'index run time {end-start}')
    
    # 通过改变源函数的调用方式实现
    import time
    def index():
        time.sleep(3)
        print('澳门最大赌场上线了 性感MM在线发牌')
        
    def get_time(func):  # func = index
        start = time.time()
        func()  # index()
        end = time.time()
        print(f'index run time {end-start}')
    
    get_time(index)  # 通过函数传参的方法实现了,但是改变了源函数的调用方式
    
    # 通过闭包函数实现
    import time
    def index():
        time.sleep(3)
        print('澳门最大赌场上线了 性感MM在线发牌')
    
    def outter(func):  # func = 原始的index
        def get_time():  # 无参装饰器
            start = time.time()
            func()
            end = time.time()
            print(f'index run time {end-start}')
        return get_time
    
    index = outter(index)  # get_time
    index()  # func()= index()
    

    完善装饰器

    上述的装饰器,最后调用index()的时候,其实是在调用get_time(),因此如果原始的index()有返回值的时候,get_time()函数的返回值应该和index()的返回值相同,也就是说,我们需要同步原始的index()和get_time()方法的返回值。

    import time
    def index():
        time.sleep(3)
        print('澳门最大赌场上线了 性感MM在线发牌')
        return 123
    
    def outter(func):
        def get_time():
            start = time.time()
            res = func()
            end = time.time()
            print(f'index run time {end-start}')
            return res
        return get_time
    
    index = outter(index)  # get_time
    res = index  # <function outter.<locals>.get_time at 0x079D2DB0>
    print(res)
    # index()
    res2 = index()
    print(res2)  # 123
    
    澳门最大赌场上线了 性感MM在线发牌
    index run time 3.000171661376953
    

    如果我们想要该装饰器取装饰一个有参函数

    def login(name):
        time.sleep(1)
        print(f'{name} is sb')
        return 'login'
    

    函数参数的问题:

    无参函数和有参函数都可以直接使用,函数可以以接收任意数量的参数。

    如果原始的login()方法需要传参,那么我们之前的装饰器是无法实现该功能的,由于login = outer(login),实际上outer(login)是等于get_time的,所以,要想login()传参,只需要在get_time()中传参即可。get_time(*args,**kwargs),实际上就是将原始的login(name)中的位置实参用元组接收,关键字实参用字典接收,并赋值给args,kwargs,fun(*args,**kwargs)将赋值的args,kwargs即(元组和字典)打散成位置形参传给login的形参。

    def index():
        time.sleep(3)
        print('澳门最大赌场上线了 性感MM在线发牌')
        return 'index'
    
    # res1 = index()
    
    def login(name):
        time.sleep(1)
        print(f'{name} is sb')
        return 'login'
    
    # res = login('egon')
    
    def outter(func):
        def get_time(*args,**kwargs):
            start = time.time()
            res = func(*args,**kwargs)
            end = time.time()
            print(f'func run time {end-start}')
            return res
        return get_time
    
    login = outter(login)  # login = get_time
    res = login('egon')  # login('egon') = get_time('egon')
    print(res)  # res = 'login'
    
    egon is sb
    func run time 1.0000572204589844
    login
    

    语法糖

    import time
    
    def outter(func):
        def get_time(*args,**kwargs):
            start = time.time()
            res = func(*args,**kwargs)
            end = time.time()
            print(f'func run time {end-start}')
            return res
        return get_time
    @outter  # 等同于 index = outter(index)  # index = get_time
    def index():
        time.sleep(3)
        print('澳门最大赌场上线了 性感MM在线发牌')
        return 'index'
    
    # index = outter(index)  # index = get_time
    # print(index)
    index()  # get_time() -> 执行index()
    
    
    @outter
    def login(name):
        time.sleep(1)
        print(f'{name} is sb')
        return 'login'
    
    login = outter(login)  # login = get_time
    print(login)
    login('egon')   #  get_time('egon')——>执行真正的login()
    
    @outter
    def home(*args,**kwargs):
        time.sleep(1)
        return 'home'
    
    home = outter(home)  # home = get_time()
    print(home)
    home()  # get_time()——>执行真正的home()
    

    语法糖会将距紧挨着它的可调用对象的名字当做参数自动传入调用outter,自动执行outter函数

    @outter
    def index()
    	pass
    

    无参装饰器模板

    def outter(func):
        def inner(*args,**kwargs):
            print('执行被装饰函数之前,你可以做的操作')
            res = func(*args,**kwargs)
            print('执行被装饰函数之后,你可以做的操作')
            return res
        return inner
    
    import time
    user_auth = {'is_login':None}
    
    def login_deco(func):
        def wrapper(*args,**kwargs):
            if user_auth['is_login']:  # 已经登录了
                res = func(*args,**kwargs)
                return res
            else:
                username = input('请输入你的用户名>>>>>>:').strip()  # 没有登录,执行登录
                pwd = input('请输入你的密码>>>>>:').strip()
                if username == 'nick'and pwd =='123':
                    print('恭喜登录成功')
                    user_auth['is_login'] = True
                    res = func(*args,**kwargs)
                    return res
                else:
                    print('账号密码错误')
        return wrapper
    
    @login_deco
    def index(name):
        time.sleep(0.5)
        print(f'{name} is dsb')
        return 666
    
    
    @login_deco
    def home():
        time.sleep(0.5)
        print('home')
        return 9999
    
    index('nick')
    
    home()
    

    有参装饰器(最复杂就三层)

    def sanceng(data):
        def deco(func):
            def wrapper(*args,**kwargs):
                if data == 'file':
                    # 执行被装饰器函数之前你可以做的操作
                    res = func(*args,**kwargs)
                    # 执行被装饰函数之后你可以做的操作
                    return res
            return wrapper
        return deco
    

    装饰器在装饰的时候,顺序从下往上。装饰器在执行的时候,顺序从上往下。

    Never,Never,Never give up.
  • 相关阅读:
    C++细节决定成败---菜鸡程序员被前辈蹂躏历程(持续更新)
    VIM杂记——基本使用
    UE4——用Android Studio打开UE4工程
    UE4——打包时遇到Could not determine java version from 'xx.x.x'的问题
    HTML笔记——常用标签总结
    LeetCode——142. Linked List Cycle II
    Leetcode——344. Reverse String
    UE4 隐藏虚拟摇杆
    UE4——实现走近物件其上方出现互动按钮并弹出UMG的功能
    UE4——调用API实现布娃娃死亡效果
  • 原文地址:https://www.cnblogs.com/zuihoudebieli/p/11182042.html
Copyright © 2011-2022 走看看