Python:闭包(简介、使用方法、nonlocal修改闭包内使用的外部变量)、装饰器(定义、作用、通用装饰器、多个装饰器、带参数的装饰器、类装饰器、装饰器方式添加WEB框架的路由)

一、闭包的介绍

在这里插入图片描述
闭包可以保存函数内的变量
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
当闭包执行完毕,外部函数的变量才释放。
在这里插入图片描述

# 闭包的作用:可以保存外部函数的变量
# 闭包的形成条件
# 1.函数嵌套
# 2.内部函数使用了外部函数的变量或者参数
# 3.外部函数返回内部函数。这个使用了外部函数变量的内部函数称为闭包

# 1.函数嵌套
def func_out():  # 外部函数
    num1 = 10
    def func_inner(num2):  # 内部函数
        result = num1 + num2  # 2.内部函数使用外部函数的变量或者参数
        print("结果为:{}".format(result))

    return func_inner  # 3.外部函数返回内部函数,不能写func_inner()

# 获取闭包对象
new_func = func_out()  # 这个new_func就是闭包,这里的new_func=func_inner
new_func(1)  # 执行闭包
new_func(10)

输出:
结果为:11
结果为:20

二、闭包的使用

在这里插入图片描述

# 外部函数接收姓名
def config_name(name):
# 内部函数保存外部函数的参数,并且完成数据显示的组成
    def inner(msg):
        print(name + "说:" + msg)
# 外部函数返回内部函数
    return inner

# 创建闭包实例对象
tom = config_name("tom")  # 闭包一旦创建完成,外部函数的变量或参数name就定了,以后就不用再传了
jerry = config_name("jerry")
# 执行闭包
tom("哥们,过来一下,我们一起玩耍")
# 执行tom()的时候就是执行inner,在执行inner的时候传了一个参数"哥们,过来一下,我们一起玩耍"给msg,在inner函数内部组织之前一直保存的name参数
jerry("明天可以吗?")


输出:
tom说:哥们,过来一下,我们一起玩耍
jerry说:明天可以吗?

这里,通过tom = config_name("tom") 和 jerry = config_name("jerry"),实际上只用了一份代码就创建了两个实例,然后在调用tom()和jerry()的时候,print(name + "说:" + msg)也只是用了这一份代码,提高了代码复用性

三、修改闭包内使用的外部变量

简言之,闭包就是那个内部函数,修改闭包内使用的外部变量,就是在内部函数里修改外部变量。

在闭包内修改外部函数的变量,需使用nonlocal关键字。

def func_out():
    num1 = 10
    def func_inner():  # 1.函数嵌套
        num1 = 20  # 在闭包内修改外部函数的变量
        result = num1 + 10  # 2.内部函数使用外部函数变量
        print(result)
    return func_inner  # 3.返回内部函数

new_func = func_out()  # 创建一个闭包实例
new_func()  # 调用闭包实例

输出:30
def func_out():
    num1 = 10
    def func_inner():  # 1.函数嵌套
        num1 = 20  # 在闭包内修改外部函数的变量
        result = num1 + 10  # 2.内部函数使用外部函数变量
        print(result)

    print("修改前的外部变量:", num1)
    func_inner()
    print("修改后的外部变量:", num1)

    return func_inner  # 3.返回内部函数

new_func = func_out()  # 创建一个闭包实例

输出:
修改前的外部变量: 10
30
修改后的外部变量: 10

可见,在创建闭包的时候程序就已经运行了,并且在内部函数里不能直接修改外部函数的变量值

def func_out():
    num1 = 10
    def func_inner():  # 1.函数嵌套
        global num1
        num1 = 20  # 在闭包内修改外部函数的变量
        result = num1 + 10  # 2.内部函数使用外部函数变量
        print(result)

    print("修改前的外部变量:", num1)
    func_inner()
    print("修改后的外部变量:", num1)

    return func_inner  # 3.返回内部函数

new_func = func_out()  # 创建一个闭包实例

输出:
修改前的外部变量: 10
30
修改后的外部变量: 10

可见,global并不能修改外部函数的变量值(global是与全局变量关联的)

def func_out():
    num1 = 10
    def func_inner():  # 1.函数嵌套
        nonlocal num1
        num1 = 20  # 在闭包内修改外部函数的变量
        result = num1 + 10  # 2.内部函数使用外部函数变量
        print(result)

    print("修改前的外部变量:", num1)
    func_inner()
    print("修改后的外部变量:", num1)

    return func_inner  # 3.返回内部函数


new_func = func_out()  # 创建一个闭包实例

输出:
修改前的外部变量: 10
30
修改后的外部变量: 20

可见,nonlocal可以修改成功

小结:在闭包内修改外部函数的变量,需使用nonlocal关键字

.
.
.
.
.
.

四、装饰器

装饰器是一个函数,嵌套函数,本质上是一个闭包函数。
在这里插入图片描述
原函数如下,

# 装饰器的目的:对已有函数进行额外的功能扩展,装饰器本质上是一个闭包函数,也是函数嵌套
# 装饰器的特点:
# 1.不修改已有函数的源代码
# 2.不修改已有函数的调用方式
# 3.给已有函数添加额外的功能

def comment():
    print("发表评论")
    
comment()

需求:在“发表评论”前,验证是否已登录,装饰器写法:

# 装饰器的目的:对已有函数进行额外的功能扩展,装饰器本质上是一个闭包函数,也是函数嵌套
# 装饰器的特点:
# 1.不修改已有函数的源代码
# 2.不修改已有函数的调用方式
# 3.给已有函数添加额外的功能
def decorator(func):  # 如果闭包函数的参数有且只有一个并且是函数类型,那么这个闭包函数是装饰器
    def inner():
        print("已添加登录验证")
        func()  # 在内部函数里面对已有函数进行装饰,这里func()=comment()
    return inner


def comment():
    print("发表评论")


comment = decorator(comment)  # 调用装饰器对已有函数进行装饰,comment=func,然后comment=inner
comment()  # 实际是执行inner()

输出:
已添加登录验证
发表评论

在这里插入图片描述
在这里插入图片描述

# 装饰器的目的:对已有函数进行额外的功能扩展,装饰器本质上是一个闭包函数,也是函数嵌套
# 装饰器的特点:
# 1.不修改已有函数的源代码
# 2.不修改已有函数的调用方式
# 3.给已有函数添加额外的功能
def decorator(func):  # 如果闭包函数的参数有且只有一个并且是函数类型,那么这个闭包函数是装饰器
    print("装饰器执行了")
    def inner():
        print("已添加登录验证")
        func()  # 在内部函数里面对已有函数进行装饰,这里func()=comment()
    return inner


# 装饰器语法糖写法:@装饰器名称。装饰器语法糖写法更简单
@decorator  # @装饰器名称,此处实际是封装了comment = decorator(comment),以后comment=inner
def comment():
    print("发表评论")


comment()  # 实际是执行inner()

# 装饰器的执行时间:当当前模块加载完成,装饰器会立即执行对已有函数进行装饰

输出:
装饰器执行了
已添加登录验证
发表评论

1、@decorator等价于comment = decorator(comment)

2、装饰器的执行时间:当当前模块加载完成,装饰器会立即执行对已有函数进行装饰

3、命名上面文件名为mydecorator.py,当在别的文件中时

import mydecorator,模块加载完成,即立即执行装饰
mydecorator.comment(),这里实际是已装饰完成innter()函数

在这里插入图片描述

五、装饰器的使用

在这里插入图片描述

import time

def decorator(func):
    def inner():  # 内部函数对已有函数进行装饰
        begin = time.time()  # 获取距离1970-1-1-0:0:1的时间差
        func()  # func() = 最早的work()
        end = time.time()
        dur = end - begin
        print("函数执行完成耗时:", dur)
    return inner

@decorator  # work = decorator(work), work = inner
def work():
    for i in range(10000):
        print(i)

work()  # 装饰完成,work = inner

六、通用的装饰器

通用的装饰器,这个装饰器可以装饰任意函数

规则:使用装饰器装饰已有函数的时候,内部函数的类型和被装饰函数的类型要保持一致
被装饰的函数有参数,内部函数也要给参数;
被装饰的函数有n个参数,内部函数也要给n个参数;
被装饰的函数有返回值,内部函数也要有返回值

# 通用的装饰器:可以装饰任意类型的函数
# -------装饰有参数的函数----------
def decorator(func):
    def inner(a, b):
        print("正在努力执行加法计算")
        func(a, b)
    return inner

#用装饰器语法糖方式装饰带参数的函数
@decorator  # 等价于 add_num = decorator(add_num), add_num = inner
def add_num(num1, num2):
    result = num1 + num2
    print("结果为:", result)

add_num(1, 2)  # 即执行inner

输出:
正在努力执行加法计算
结果为: 3
# 通用的装饰器:可以装饰任意类型的函数
# -------装饰有参数和有返回值的函数----------
def decorator(func):
    def inner(a, b):
        print("正在努力执行加法计算")
        num = func(a, b)
        return num
    return inner

#用装饰器语法糖方式装饰带参数的函数
@decorator  # 等价于 add_num = decorator(add_num), add_num = inner
def add_num(num1, num2):
    result = num1 + num2
    return result

result = add_num(1, 2)  # 即执行inner
print("结果为:", result)

输出:
正在努力执行加法计算
结果为: 3

通用的装饰器:

# 通用的装饰器:可以装饰任意类型的函数
# -------装饰有不定长参数和有返回值的函数----------
def decorator(func):
    def inner(*args, **kwargs):
        print("正在努力执行加法计算")
        # *args,把元祖里的每一个元素按位置参数传参
        # **kwargs,把字典里的每一个键值对按关键字方式传参
        # 这里对元祖和字典进行拆包,仅限于被装饰函数为不定长参数的函数使用
        num = func(*args, **kwargs)
        return num
    return inner

#用装饰器语法糖方式装饰带参数的函数
@decorator  # 等价于 add_num = decorator(add_num), add_num = inner
def add_num(*args, **kwargs):
    result = 0
    # *args;元组类型;**kwargs:字典类型
    for value in args:
        result += value
    for value in kwargs.values():
        result += value

    return result

result = add_num(1, 2)  # 即执行inner
print("结果为:", result)

输出:
正在努力执行加法计算
结果为: 3

通用装饰器的使用:

# 通用的装饰器:可以装饰任意类型的函数
# -------装饰有不定长参数和有返回值的函数----------
def decorator(func):
    def inner(*args, **kwargs):
        print("正在努力执行加法计算")
        # *args,把元祖里的每一个元素按位置参数传参
        # **kwargs,把字典里的每一个键值对按关键字方式传参
        # 这里对元祖和字典进行拆包,仅限于被装饰函数为不定长参数的函数使用
        num = func(*args, **kwargs)
        return num
    return inner

#用装饰器语法糖方式装饰带参数的函数
@decorator  # 等价于 add_num = decorator(add_num), add_num = inner
def add_num(*args, **kwargs):
    return "哈哈"

result = add_num()  # 即执行inner
print("结果为:", result)

输出:
正在努力执行加法计算
结果为: 哈哈

七、多个装饰器的使用

# 定义装饰器
def make_p(func):
    def inner():
        # 在内部函数对已有函数进行装饰
        result = "<p>" + func() + "</p>"  # func = content
        return result
    return inner

@make_p  # content = make_p(content), content = inner
def content():
    return "人生苦短,我用Python!"

result = content()  # content = inner
print(result)

输出:
<p>人生苦短,我用Python!</p>
# 定义装饰器
def make_p(func):
    print("make_p装饰器执行了")
    def inner():
        # 在内部函数对已有函数进行装饰
        result = "<p>" + func() + "</p>"  # func = content
        return result
    return inner

def make_div(func):
    print("make_div装饰器执行了")
    def inner():
        # 在内部函数对已有函数进行装饰
        result = "<div>" + func() + "</div>"  # func = content
        return result
    return inner

@make_div
@make_p  # content = make_p(content), content = inner
def content():
    return "人生苦短,我用Python!"

result = content()  # content = inner
print(result)

输出:
make_p装饰器执行了
make_div装饰器执行了
<div><p>人生苦短,我用Python!</p></div>

小结:

多个装饰器的过程:由内到外装饰,先执行内部的装饰器,再执行外部的装饰器
content = make_div(make_p(content))

分步拆解:
1、content = make_p(content),内部装饰器装饰完成,content = make_p.inner
2、content = make_div(make_p.inner)

在这里插入图片描述

八、带参数的装饰器

在这里插入图片描述

def decorator(func):  # 装饰器只能接收一个参数并且是函数类型
    def inner(a, b):
        print("正在执行加法计算")
        func(a, b)
    return inner

@decorator
def add_num(a, b):
    result = a + b
    print(result)

@decorator
def substract_num(a, b):
    result = a - b
    print(result)

add_num(1, 2)
substract_num(1, 3)

输出:
正在执行加法计算
3
正在执行加法计算  # 这句话是加法计算,不是我们想要的
-2

改进如下,

def return_decorator(flag):  # return_decorator是一个普通函数
    def decorator(func):  # 装饰器只能接收一个参数并且是函数类型
        def inner(a, b):
            if flag == "+":
                print("正在执行加法计算")
            elif flag == "-":
                print("正在执行减法计算")
            func(a, b)
        return inner
    return decorator  # 当调用函数的时候返回一个装饰器decorator

# return_decorator("+")函数返回decorator,→ @decorator => add_num = decorator(add_num)
@return_decorator("+")
def add_num(a, b):
    result = a + b
    print(result)

@return_decorator("-")
def substract_num(a, b):
    result = a - b
    print(result)

add_num(1, 2)
substract_num(1, 3)

输出:
正在执行加法计算
3
正在执行减法计算
-2

1、装饰器只能接收一个参数并且是函数类型
2、带参数的装饰器,其实就是在装饰器外面套了一个普通函数,让函数接收参数,在函数内部返回的是一个装饰器,再用装饰器去装饰函数

在这里插入图片描述

九、类装饰器

在这里插入图片描述

# 类装饰器,使用类装饰已有函数
class MyDecorator:
    def __init__(self, func):
        self.func = func  # func = show

    # 实现__call__()方法,让对象变成可调用对象,可调用的对象能够像函数使用
    def __call__(self, *args, **kwargs):
        print("课已讲完")
        self.func()

@MyDecorator  # @MyDecorator => show = MyDecorator(show)
def show():
    print("快下课了")

show()  # 执行show()→执行MyDecorator类创建类实例对象,show()=>对象()
print(show)

输出:
课已讲完
快下课了
<__main__.MyDecorator object at 0x000002BF218D8048>

拓展:函数能够被调用,是因为函数自带__call__方法

def test():
    print("哈哈")

print(dir(test))  # dir(),返回函数内部的属性和方法,返回一个列表

输出:
['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']

十、装饰器方式添加路由

urlpatterns = [
    path('admin/', admin.site.urls),
    path("index/", views.index),
    path("login/", views.myview.as_view())

]

示例,index函数已开发完毕,现在要在index的基础上增加新的功能,可以使用装饰器,因为需要同时告知router,因此是带参数的装饰器。

def route(path):
    def decorator(func):  # 装饰器要装饰函数,所以这里要有func形参并且是函数类型
        # 当执行装饰器的时候就需要把路由添加到路由列表里,相当于此时已经urlpatterns.append(route, view.index)
        urlpatterns = []
        urlpatterns.append((path, func))
        def inner(request):
            print("这是装饰器方式添加的路由")
            result = func(request)
            return result
        return inner
    return decorator

@route("index/")  # 等价于@decorator,接着index = decorator(index),接着index = inner
def index(request):  # 视图函数负责处理业务逻辑,request对象封装所有请求信息
    print(request)
    return HttpResponse("我的首页呀,哈哈")

class myview(View):
    def get(self, request):
        return HttpResponse("这是基于类的视图实现方式")

PS: source, bilibili

猜你喜欢

转载自blog.csdn.net/weixin_47008635/article/details/115281972