014.Python闭包函数与装饰器

一、闭包函数

(一)什么是闭包函数

​ 闭包函数=函数嵌套定义+函数对象+名称空间与作用域

​ 闭:指的是该函数是定义在一个函数内部的函数。

​ 包:指的是该函数访问了一个来自外层函数的变量。

(二)为函数体代码传参的两种方式

1.直接使用参数的形式传递

​ 直接通过函数的参数传递,给形参传值。

def func(x):  # 设定参数
    print(x)

func(1)  # 直接传参
func(2)
func(3)

2.闭包函数的形式

​ 通过外层包一个函数,将需要的参数传给它。

def outter():
    x = 1111  # 闭函数访问了一个外层函数的变量,是为包。

    def inner():  # 定义了一个函数内的函数,是为闭。 这里的inner就是闭包函数的内存地址
        print(x)

    return inner  # 千万不要加括号,否则会执行函数体代码
	# 返回闭包函数的内存地址
f = outter()
# print(f)  # <function outter.<locals>.inner at 0x000001ACE6E79040>

def f3():
    x=222222
    f()

f3()  # 1111

二、函数的装饰器

(一)什么是装饰器

​ 器:指的是工具。

​ 装饰:指的是为被装饰对象添加额外的功能。

​ 装饰器就是为其他函数添加新功能的函数。

(二)为何要用装饰器

​ 软件开发需要遵循开放封闭原则,装饰器可以在不修改被装饰器对象,即被装饰函数的定义与调用方式都不改变的前提下,为被装饰函数添加新的功能。

​ 开放:指的是对拓展新功能开放。

​ 封闭:指的是对修改源代码封闭。

(三)如何使用装饰器

1.无参装饰器的实现

(1)从定义函数开始,根据需求逐步改进,实现装饰器

①装饰器初探

# 定义函数
import time

def index(x, y):
    time.sleep(2)
    print("This is index===>",x,y)

index(1,2)  # This is index===> 1 2

def home(name):
    time.sleep(1)
    print("This is page of ==>",name)

home("zhangsan")  # This is page of ==> zhangsan
# 实现一个简单的装饰器
# 1) 首先要实现最基本的功能记录时间的功能
# 2) 然后,通过一个闭包函数,实现调用外层函数outter(),返回内层函数wrapper的内存地址
# 3) 把outter()赋值给index,这个index就相当于outter函数的返回值wrapper
# 4) 给index加括号之后,可以触发wrapper函数运行,当wrapper这个局部函数运行时,会寻找它自己的内层函数需要的变量x,
# 5) wrapper函数在定义的时候,确定了参数x是外层变量,所以去外层空间寻找,找到变量值index,由于在调用wrapper的时候,已经传了index需要的两个参数,所以index函数触发调用,
# 6) index层层向上寻找,在全局作用域找到index函数,运行对用的功能,结合wrapper增加的功能,得出了我们需要的结果。但是,这里有一个问题还没有解决,就是这个工具,是针对index定制的,不能用于home等其他函数,需要进行改进。

def outter():
    x = index

    def wrapper(a,b):
        start_time = time.time()  
        x(a,b)
        stop_time = time.time()
        print("run time is :%s" %(stop_time-start_time))

    return wrapper  # 不能加括号,否则会触发函数运行!

index = outter()

index(1,2)  # This is index===> 1 2
            # run time is :2.000650405883789

②装饰器改进1

# 装饰器改进1
# 1) 因为初步版本的装饰器,受限于传参个数问题,以及无法将函数调用给其他函数使用,所以做了如下改进:
# 2) 通过使用*与**将wrapper函数的形式参数变为可变长度的参数,
# 3) 再把wrapper函数内部的函数x的实际参数变为可变长度参数,这样就实现了,wrapper接收的任何函数,都可以原样传给目标函数x,
# 4) 在wrapper函数的外层,将写死的x=函数名去掉,使用外层函数outter进行接收参数:目标函数名,
# 5) 至此,实现了使用index/home 赋值给outter(index/home),即index/home等同于outter函数的返回值wrapper,加上括号传参后触发运行,由于后面有括号,且可以传任意参数,wrapper函数开始运行,寻找变量x,找到外层函数outter传入的参数,继续运行,最终得出我们想要的结果。
# 6) 但是,仍然还是有问题,装饰后的函数,无法给出与原函数一样的返回值,目前的函数返回值为None,需要继续改进。
import time

def index(x, y):
    time.sleep(2)
    print("This is index===>",x,y)
	return "index的返回值"

def home(name):
    time.sleep(1)
    print("This is page of ==>",name)
    return "home的返回值"

def outter(x):
    # x = home
    def wrapper(*args,**kwargs):
        start_time = time.time()
        x(*args,**kwargs)
        stop_time = time.time()
        print("run time is :%s" %(stop_time-start_time))
    return wrapper

index = outter(index)
home = outter(home)

index(1,2)  # wrapper(1, 2)
            # This is index===> 1 2
            # run time is :2.000706195831299
home("zhangsan")    # wrapper("zhangsan")
                    # This is page of ==> zhangsan
                    # run time is :1.0000085830688477

③装饰器改进2

# 装饰器改进2
# 1) 初步改进版的装饰器函数,无法给出与原函数一样的返回值,所以做了如下改进:
# 2) 先给我们wrapper内的函数赋值,
# 3) 再给wrapper函数一个返回值,就是我们赋值的的那个变量,
# 4) 至此,我们实现了使用outter()给我们想要的函数进行装饰,增加装饰功能的后,不影响原函数的功能,不修改原函数的代码,不改变原函数的调用方式,并且可以输出原函数的结果,以及得出与原函数相同的返回值
# 5) 虽然如次使用在功能上没有问题,但是,每次使用都要先使用与目标函数相同的函数名,去给装饰器函数去赋值并传参目标函数名,然后才可以像装饰目标函数一样调用,显得有些麻烦。

import time

def index(x, y):
    time.sleep(2)
    print("This is index===>",x,y)
    return "index的返回值"

def home(name):
    time.sleep(1)
    print("This is page of ==>",name)
    return "home的返回值"

def outter(x):
    # x = home
    def wrapper(*args,**kwargs):
        start_time = time.time()
        res = x(*args,**kwargs)
        stop_time = time.time()
        print("run time is :%s" %(stop_time-start_time))
        return res
    return wrapper

index = outter(index)  # index=wrapper
home = outter(home)  # home=wrapper

res1 = index(1,2)   # wrapper(1, 2)
                    # This is index===> 1 2
                    # run time is :2.000706195831299
print("====>",res1) # ====> index的返回值

res2 = home("zhangsan")     # wrapper("zhangsan")
                            # This is page of ==> zhangsan
                            # run time is :1.0000085830688477
print("====>",res2)         # ====> home的返回值

④语法糖的使用

# 装饰器改进3  使用语法糖
# 1) 使用语法糖,可以达成如下效果:
# 2) 省略了使用与被装饰函数相同的变量名,给外层函数赋值的过程,这个过程是实现装饰器的关键,即index = outter(index)——>index=wrapper
# 3) 
import time

def outter(x):
    # x = home
    def wrapper(*args,**kwargs):
        start_time = time.time()
        res = x(*args,**kwargs)
        stop_time = time.time()
        print("run time is :%s" %(stop_time-start_time))
        return res
    return wrapper  # 千万不能加括号,否则会触发运行

@outter  # 替代了  # index = outter(index)  # index=wrapper
def index(x, y):
    time.sleep(2)
    print("This is index===>",x,y)
    return "index的返回值"

@outter  # 替代了  # home = outter(home)  # home=wrapper
def home(name):
    time.sleep(1)
    print("This is page of ==>",name)
    return "home的返回值"

res1 = index(1,2)   # wrapper(1, 2)
                    # This is index===> 1 2
                    # run time is :2.000706195831299
print("====>",res1) # ====> index的返回值

res2 = home("zhangsan")     # wrapper("zhangsan")
                            # This is page of ==> zhangsan
                            # run time is :1.0000085830688477
print("====>",res2)         # ====> home的返回值

⑤装饰器的一般模板

# 目前阶段的装饰器,可以形成一个模板,给函数提供装饰功能
def outter(func):
    def wrapper(*args,**kwargs):
        res = func(*args,**kwargs)
        return res
    return wrapper

# 装饰器:计算函数运行时间
def timmer(func):
    def wrapper(*args, **kwargs):
        start=time.time()
        res = func(*args, **kwargs)
        stop=time.time()
        print(stop -start)
        return res
    return wrapper

# 装饰器:函数登陆认证后可以使用功能
def auth(func):
    def wrapper(*args,**kwargs):
        name = input("请输入用户名>>>: ").strip()
        pwd = input("请输入您的密码>>>:").strip()
        if name == "egon" and pwd == "123":
            print("用户登录成功")
            res = func(*args,**kwargs)
            return res
        else:
            print("账户名或密码错误")
    return wrapper

@auth  # 替代了  # index = outter(index)  # index=wrapper
def index(x, y):
    time.sleep(2)
    print("This is index===>",x,y)
    return "index的返回值"

@auth  # 替代了  # home = outter(home)  # home=wrapper
def home(name):
    time.sleep(1)
    print("This is page of ==>",name)
    return "home的返回值"

⑥wraps装饰器的使用

# 为了让装饰器更加完美,跟原函数相比更加相似,我们可以对装饰器进一步优化,使用wraps装饰器来装饰我们的wrapper函数,在装饰后的函数值相同的情况下,进一步,让被装饰后的函数的文档注释等等细节属性,跟原函数保持一致,让被装饰器装饰的函数,与原函数无限接近。

from functools import wraps

def timmer(func):
    @wraps(func)
    def wrapper(*args,**kwargs):
        start = time.time()
        res = func(*args, **kwargs)
        stop = time.time()
        print(stop - start)
        return res
    return wrapper
@timmer # index = timmer(index) # index = wrapper
def index(x, y):
    """
    这是index的文档注释
    """
    time.sleep(1)
    print("index===>>>",x,y)
    return "index的返回值"

index(1,2)   # index===>>> 1 2
             # 1.0009651184082031
print(index.__name__)  # index
help(index)   # 查看函数的文档注释
			  # Help on function index in module __main__:

              # index(x, y)
              #     这是index的文档注释
(2)完整的无参装饰器模板
from functools import wraps

def outter(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        res = func(*args, **kwargs)
        return res
    return wrapper

2.叠加多个装饰器

(1)如何同时叠加多个装饰器

​ 在被装饰函数的上方,依次按行进行语法糖操作@装饰器名字。

def deco1(func1):  # func1 = wrapper2
    def wrapper1(*args, **kwargs):
        print('=======>wrapper1')
        res1 = func1(*args, **kwargs)  #wrapper2==》未完
        return res1
    return wrapper1

def deco2(func2):  # func2 = wrapper3
    def wrapper2(*args, **kwargs):
        print('=======>wrapper2')
        res2 = func2(*args, **kwargs)  # wrapper3==》未完
        return res2
    return wrapper2

def deco3(func3):  # func3 = index
    def wrapper3(*args, **kwargs):
        print('=======>wrapper3')
        res3 = func3(*args, **kwargs)  # index==》未完
        return res3
    return wrapper3

        # index = wrapper1


@deco1  # deco1(wrapper2)=>wrapper1
@deco2  # deco2(wrapper3)=>wrapper2
@deco3  # deco3(index)=>wrapper3
def index():
    print("from index")
    return 123


res=index()  # res=wrapper1()

# =======>wrapper1
# =======>wrapper2
# =======>wrapper3
# from index
(2)总结:加载顺序与执行顺序

​ 多个装饰器叠加时,自下而上加载,自上而下执行。

import time


def timmer(func):
    def wrapper1(*args, **kwargs):
        print('===>wrapper1')
        start = time.time()
        res = func(*args, **kwargs)
        stop = time.time()
        print(stop - start)
        return res

    return wrapper1


def auth(func):
    def wrapper2(*args, **kwargs):
        print('===>wrapper2')
        name = input("请输入您的账号:").strip()
        pwd = input("请输入您的账号:").strip()
        if name == "egon" and pwd == "123":
            print('登录成功')
            res = func(*args, **kwargs)
            return res
        else:
            print("账号密码错误")

    return wrapper2


@auth
@timmer
def index():
    time.sleep(1)
    print("from index")
    return 123


# wrapper2=>wrapper1=>index

index()  # wrapper1()

3.有参装饰器的实现

(1)什么是有参装饰器

​ 在无参装饰器的基础上,再增加一层外函数,用于传递一个新的参数,这样的三层函数的装饰器就是有参装饰器。

(2)为何要使用有参装饰器

​ 无参装饰器实现了与原函数一样的调用方式,并通过闭包函数实现了原函数参数不被改变,当我们需要一个新的功能,而这个新的功能有需要一个参数的时候,无参装饰器就无法满足装饰器的原则,因此需要有参装饰器去实现这个功能。

# 无参装饰器  两层闭包无法给被装饰函数传递该函数本身需要参数之外的值
import time

def outter(func):
    def wrapper(*args, **kwargs):
        start = time.time()
        res = func(*args, **kwargs)
        stop = time.time()
        print(stop - start)
        return res
    return wrapper

# @函数的内存地址1(参数1,参数2,参数3...)  # 语法糖格式@的作用是给函数的内存地址加括号,并把被装饰的函数index当作参数传入装饰器的第二层函数,wrapper就会带着参数进入装饰器执行。
def index(x, y):
    print('index===>', x, y)

@outter
def home(name):
    print('home====>', name)

(3)如何使用有参装饰器
①示例,演示有参装饰器的实现
# 示范一: 无参装饰器,验证信息的来源无法选择
def outter(func):
    def wrapper(*args, **kwargs):
        inp_name = input("please input your username: ").strip()
        inp_pwd = input("please input your password: ").strip()
        with open("db.txt",mode="rt",encoding="utf-8") as f:
            for line in f:
                name_db, pwd_db, balance_db = line.strip("\n").split(":")
                if inp_name == name_db and inp_pwd == pwd_db:
                    print("login successful")
                    res = func(*args, **kwargs)
                    return res
            else:
                print("login failed")

    return wrapper

@outter
def index(x, y):
    print('index===>', x, y)

index(1, 2)
# 示范二:增加用户验证数据的多个来源
# ldap
# mysql
# file
def outter2(mode):
    def outter(func):
        def wrapper(*args, **kwargs):
            inp_name = input("please input your username: ").strip()
            inp_pwd = input("please input your password: ").strip()
            if mode == "file":
                print("认证来源=====》file")
                with open("db.txt",mode="rt",encoding="utf-8") as f:
                    for line in f:
                        name_db, pwd_db, balance_db = line.strip("\n").split(":")
                        if inp_name == name_db and inp_pwd == pwd_db:
                            print("login successful")
                            res = func(*args, **kwargs)
                            return res
                    else:
                        print("login failed")
            elif mode == "mysql":
                print("认证来源=====》mysql")
            elif mode == "ldap":
                print("认证来源=====》ldap")
            else:
                print("未知认证来源")

        return wrapper
    return outter

outter = outter2(mode="mysql")

@outter  #语法糖@触发加括号运行,并将被装饰的函数名作为参数传进去并赋值给被装饰函数名, 实现步骤:@outter===>  index=outter(index) ====> index = wrapper
def index(x, y):
    print('index===>', x, y)

index(1, 2)
# 示范三:语法糖加载方式简化
def outter2(mode):
    def outter(func):
        def wrapper(*args, **kwargs):
            inp_name = input("please input your username: ").strip()
            inp_pwd = input("please input your password: ").strip()
            if mode == "file":
                print("认证来源=====》file")
                with open("db.txt",mode="rt",encoding="utf-8") as f:
                    for line in f:
                        name_db, pwd_db, balance_db = line.strip("\n").split(":")
                        if inp_name == name_db and inp_pwd == pwd_db:
                            print("login successful")
                            res = func(*args, **kwargs)
                            return res
                    else:
                        print("login failed")
            elif mode == "mysql":
                print("认证来源=====》mysql")
            elif mode == "ldap":
                print("认证来源=====》ldap")
            else:
                print("未知认证来源")

        return wrapper
    return outter



@outter2(mode="mysql")  # outter2(mode="mysql")就是返回值outter,语法糖@触发加括号运行 outter(index)=index ====》index = wrapper
def index(x, y):
    print('index===>', x, y)

index(1, 2)  # wrapper(1, 2)
②完整的有参装饰器模板
def outter2(x, y, z, a, b):
    def outter1(func):
        def wrapper(*args, **kwargs):
            res = func(*args, **kwargs)
            return res
        return wrapper
    return outter1
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值