Python——闭包与装饰器(九)

发布时间 2023-03-22 21:09:10作者: 孙凯玉

1. 什么是闭包

Python中的闭包是一个比较模糊的概念,有很多朋友都认为不好理解,但是随着深入学习,就会发现闭包无论如何都是需要去理解的,下面我将自己对闭包的理解进行阐述,希望能够对你有所帮助 ~

我们可以将闭包理解为一种特殊的函数,这种函数由两个函数的嵌套组成,且称之为外函数和内函数,外函数返回值是内函数的引用,此时就构成了闭包。

闭包的格式

# coding=utf-8
# 闭包
# def func1():
#     print ("函数1运行")
#     return func2()
# #函数1的返回值是函数2的引用
# def func2():
#     print ("函数2运行")
#     return 2
# r =func1()
# print (r)
# r2= r()  # r = func2
# print (r2)


def func1():
    print ("函数1运行")
    def func2():
        print ("函数2运行")
    func2()
    return func2()
f2 = func1()
print(f2)
f2()
  
"""
在一个函数,比如func1中的内部定义了另外一个函数function2
并且函数1(func1)的返回值是函数2(func2)的引用
这种情况,我们称之为闭包

简单来说就是外部函数返回内部函数的引用就叫做闭包
"""

输出print打印结果:

函数1运行
函数2运行
函数2运行
None

# 闭包的实例:龟兔赛跑

# coding=utf-8
import time
import random

# 定义跑道长度
track_length = 10
def runtime(func):
    def wrapper():
        start_time = time.time()
        func()
        end_time = time.time()
        print (func.__name__,"运行时间是",end_time-start_time,"秒")
    return wrapper
    
@runtime
def tortoise():
    # for i in [1,2,3,4,5,6,7,8,9,10]:
    for i in range(1,track_length+1):
        print ("乌龟跑的{}米".format(i))
        time.sleep(1)
@runtime
def rabbit():
    for i in range(1,track_length + 1):
        if i % 5 == 0:
            time.sleep(random.randint(1,10))
        print ("兔子跑了{}米".format(i))
tortoise()
rabbit()

print打印结果:

乌龟跑的1米
乌龟跑的2米
乌龟跑的3米
乌龟跑的4米
乌龟跑的5米
乌龟跑的6米
乌龟跑的7米
乌龟跑的8米
乌龟跑的9米
乌龟跑的10米
tortoise 运动时间是 10.04876708984375 秒 
兔子跑了1米
兔子跑了2米
兔子跑了3米
兔子跑了4米
兔子跑了5米
兔子跑了6米
兔子跑了7米
兔子跑了8米
兔子跑了9米
兔子跑了10米
rabbit 运动时间是 9.022485494613647 秒

修改外函数的变量值

想要修改外函数的变量值,需要用到nonlocal关键字。

def outfunc(a):

    def infunc(b):
        nonlocal a
        a = a*2
        return a*b

    return infunc

func_instance = outfunc(8)
print(type(func_instance))
res = func_instance(10)
print(res)

输出

➜  test git:(master) ✗ python3 testpy.py
<class 'function'>
160

如上即可。

闭包的使用场景

Python中,闭包的主要用途就是用于装饰器的实现。后续讲解。

还有就是可以简化参数重复传递,比如:

def add(a,b,c):
    print(a*b*c)

add(1,2,1)
add(1,2,2)
add(1,2,3)
add(1,2,4)
add(1,2,5)
复制代码

输出

➜  test git:(master) ✗ python3 testpy.py 
2
4
6
8
10

你会发现,a和b是固定不变的,我们怎么样才能减少a和b的传参,而只改变c的值呢?这个时候闭包就起到了作用。

def addNew(a,b):

    def addC(c):
        return a*b*c

    return addC
            
func_ins = addNew(1,2)
print(func_ins(1))
print(func_ins(2))
print(func_ins(3))
print(func_ins(4))
print(func_ins(5))

输出

➜  test git:(master) ✗ python3 testpy.py
2
4
6
8
10

2. 什么是装饰器呢?

就是在特定条件下为某些函数再不改动函数体的时候为函数新添加一些功能,这就是装饰器

实现原理:
基于@语法和函数闭包,将原函数封装在闭包中,然后将函数赋值为一个新的函数(内置函数),执行函数时再在内层函数中执行闭包中的原函数

实现效果:
可以在你改变函数内部代码和调用的前提下,实现在函数执行和执行拓展功能

适用场景:
多个函数系统统一在执行前后定义一些功能

关于前言我们了解这么多就够了,然后小编带着大家推导出装饰器
装饰器:
装饰器的写法:
这里我们有一个需求,我们定义了5个函数,想在5个函数执行前和执行后都打印一句话:装饰器的学习。首先我们来写于一下没有装饰器的写法,话不多说直接上代码:

def a():
    pass
 
 
def b():
    pass
 
 
def c():
    pass
 
 
def d():
    pass
 
 
def e():
    pass

先定义5个函数,再加上我们要打印的话:

def a():
    print("装饰器的学习")
    print("装饰器的学习")
 
 
def b():
    print("装饰器的学习")
    print("装饰器的学习")
 
 
def c():
    print("装饰器的学习")
    print("装饰器的学习")
 
 
def d():
    print("装饰器的学习")
    print("装饰器的学习")
 
 
def e():
    print("装饰器的学习")
    pass
    print("装饰器的学习")
 
 
a()
b()
c()
d()
e()

运行一下:

1675421215622.png

发现运行成功,但我们想如果我要修改打印的话就要都修改一次,特别麻烦,而且,这是5个函数如果是500个,我们还要一个一个的去加吗?这就有我们的装饰器了,首先我用装饰器修改下,再给大家解释。

def outer(origin):
    def inner():
        print("装饰器的学习")
        res = origin()
        print("装饰器的学习")
        return res
 
    return inner
 
 
@outer
def a():
    pass
 
 
@outer
def b():
    pass
 
 
@outer
def c():
    pass
 
 
@outer
def d():
    pass
 
 
@outer
def e():
    pass
 
 
a()
b()
c()
d()
e()

运行一下:

1675421264764.png

发现这样我们也成功了,接下来小编来个大家解释

首先:
我们要明白@的作用,那我们的函数a来举例子@的作用就是帮我们执行一次a=outer(a),首先python将把我们的a变成参数传给outer函数,运行后再赋值给a,这就是@的作用。

其次给大家解释一下自定的outer函数
我自己称这个函数为@下函数的补丁函数,也就是装饰器函数还是拿a函数举例子,首先a函数变成参数传给了我们的outer函数,outer里又嵌套了一个inner函数 ,然后将函数a赋值给res,然后用return语句返回出结果,外层函数返回inner函数,也就是将inner函数运行一次,这就是工作流程。

最后分别在各函数前加上装饰,最后运行出结果

1675421264764.png
这就是装饰器的写法。

装饰器的参数
这时我遇到一个问题如果函数内有参数而且每个函数的参数数量不同,我们应该怎末办,先看下面代码

def outer(origin):
    def inner():
        print("装饰器的学习")
        res = origin()
        print("装饰器的学习")
        return res
 
    return inner
 
 
@outer
def a(g, e):
    pass
 
 
@outer
def b(w):
    pass
 
 
@outer
def c(u, y, t):
    pass
 
 
@outer
def d(c):
    pass
 
 
@outer
def e():
    pass
 
 
a()
b()
c()
d()
e()

这时我们运行一下

image.png

发现报错,是因为我们的装饰器内没有这两个参数,那可以在装饰器内设置两个参数,但问题是,有的函数内有3个参数,而有的函数内没有参数,那我们应该怎么办?

针对这个问题我们可以给装饰器设置动态参数,先看代码:

def outer(origin):
    def inner(*args, **kwargs):
        print("装饰器的学习")
        res = origin(*args, **kwargs)
        print("装饰器的学习")
        return res
 
    return inner
 
 
@outer
def a(a1):
    print("我是一函数")
 
 
@outer
def b(a1, a2):
    print("我是二函数")
 
 
@outer
def c(a5, a6, a7):
    print("我是三函数")
 
 
a(1)
b(2, 3)
c(4, 5, 6)

因为函数太多了,小编有点麻烦就剪了几个函数,但道理是相同的,这时我们再运行一下

1675421364035.png

这样我们就成功了,以上就是装饰器的写法,接下来给大家拓展一下

装饰器的拓展:(functools模块)
首先给大家引入一下这时教给大家几个魔法方法

1675421496515.png
接下来我们实战一下

def outer(origin):
    def inner(*args, **kwargs):
        # 我是一个装饰器函数
        print("装饰器的学习")
        res = origin(*args, **kwargs)
        print("装饰器的学习")
        return res
 
    return inner
 
 
@outer
def c(a5, a6, a7):
    # 我是个函数
    print("我是三函数")
 
 
c(4, 5, 6)
print(c.__name__)
print(c.__doc__)

运行一下:
1675421532140.png

这时我们发现我要的是c函数,但给我反馈的是inner函数,这是为什么呢?

这就是工作原理,直接就把c函数装饰成了inner函数,那以后再工作中一定会要自己函数的名字,而不要我装饰后的函数,这样就可以让我们的函数装饰的更像,其实在以后中,都想装饰的更像,那我们应该怎末办?

这时就需要我们的第三方模块functools,直接上代码

import functools
 
 
def outer(origin):
    @functools.wraps(origin)
    def inner(*args, **kwargs):
        # 我是一个装饰器函数
        print("装饰器的学习")
        res = origin(*args, **kwargs)
        print("装饰器的学习")
        return res
 
    return inner
 
 
@outer
def c(a5, a6, a7):
    # 我是个函数
    print("我是三函数")
 
 
c(4, 5, 6)
print(c.__name__)
print(c.__doc__)

这时再运行一下

1675421572219.png

这时我们发现,我们伪装成功了,这样就会让我们的装饰更像。

装饰器模板:
接下来送给大家装饰器的模板,以后需要随时ctrl+c和ctrl+v

import functools
 
 
def outer(origin):
    @functools.wraps(origin)
    def inner(*args, **kwargs):
        # 这里书写需要装饰的功能
        res = origin(*args, **kwargs)
        return res
 
    return inner