标签搜索

目 录CONTENT

文章目录

Python中的闭包和装饰器.md

小小城
2021-08-22 / 0 评论 / 0 点赞 / 8 阅读 / 7,216 字 / 正在检测是否收录...
温馨提示:
本文最后更新于 2022-05-02,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

Python中的闭包和装饰器

@[toc]

一. 闭包

1.闭包概念

1.1 python的命名空间

  •  python命名空间就是作用域表现的原因,这里我简要说明一下。
  •  引入命名空间的主要原因还是为了避免变量冲突,因为python中的模块众多,模块中又有函数,类等,它们都要使用到变量。
  •  但如果每次都要注意不和其他变量名冲突,那就太麻烦了,开发人员应该专注于自己的问题,而不是考虑别人写的程序中用到了什么变量,所以python引入了命名空间。
  •  命名空间分为模块层,模块内又分为全局作用域和局部作用域,用一个图来表示的话:
    在这里插入图片描述
  •  模块之间命名空间不同,而里面还有全局作用域和局部作用域,局部作用域之间还能嵌套,这样就能保证变量名不冲突了
  •  这里顺便补充一下,可以通过 __name__ 属性获取命名空间的名字。主文件的命名空间是叫做 '__main__',而模块的命名空间就是模块名。
  •  作用域的诞生,是因为当python在寻找一个变量的时候,首先会在当前的命名空间中寻找,如果当前命名空间中没有,就到上一级的命名空间中找,以此类推,如果最后都没找到,则触发变量没找到的异常
  •  我们之前一直说:全局作用域无法访问局部作用域,而局部作用域能够访问全局作用域就这这个原因。而当我在局部作用域创建了一个和外面同名的变量时,python在找这个变量的时候首先会在当前作用域中找,找到了,就不继续往上一级找了。

1.2 闭包的概念

  •  在早期的python版本时,局部作用域是不能访问其他的局部作用域的,只能访问全局的,而现在的版本都是依次向上一级找。

也就是因为这个特性,我们可以在内部函数中访问外部函数中的变量,这也就是所谓的闭包了。

2. 闭包解析

def test1():
    print("--- in test1 func----")

调用函数

test1()

引用函数

ret = test1

print(id(ret))
print(id(test1))

通过引用调用函数

ret()

运行结果:

--- in test1 func----
140212571149040
140212571149040
--- in test1 func----

以y=kx+b为例,请计算一条线上的某个点,即给x值计算出y值。下面以这个例子引出闭包的概念。

方法1:

#第1种
k = 1
b = 2
y = k*x+b
#缺点:如果需要多次计算,那么就的写多次y = k*x+b这样的式子

方法2:

#第2种
def line_2(k, b, x):
    print(k*x+b)

line_2(1, 2, 0)
line_2(1, 2, 1)
line_2(1, 2, 2)
#缺点:如果想要计算多次这条线上的y值,那么每次都需要传递k,b的值,麻烦

方法3

#第3种: 全局变量
k = 1
b = 2
def line_3(x):
    print(k*x+b)

line_3(0)
line_3(1)
line_3(2)
k = 11
b = 22
line_3(0)
line_3(1)
line_3(2)
#缺点:如果要计算多条线上的y值,那么需要每次对全局变量进行修改,代码会增多,麻烦
  •  这里引申一下,关于全局变量,要是直接读取,不修改的话,是不用加global的。
  •  而且所谓的修改,指的是地址变了,假如我指向的是一个列表,指向没变,列表中的内容改变了,也不用加global的。

我们看一下下面的例子:

a = 100
b = 'chenchi'
c = [1, 2, 3]

def func():
    print('a的值:{},a的地址:{}'.format(a, id(a)))
    print('b的值:{},b的地址:{}'.format(b, id(b)))
    print('c的值:{},c的地址:{}'.format(c, id(c)))

def func2():
    global a
    global b
    a += 1
    b += 'ccc'
    # 这里c不用声明global,c的地址没有发生变化
    c.append(4)

if __name__ == '__main__':
    func()
    func2()
    func()

运行结果:
在这里插入图片描述
列表的地址没变,不用加global。

方法4

# 第4种:缺省参数
def line_4(x, k=1, b=2):
    print(k*x+b)

line_4(0)
line_4(1)
line_4(2)

line_4(0, k=11, b=22)
line_4(1, k=11, b=22)
line_4(2, k=11, b=22)
# 优点:比全局变量的方式好在:k, b是函数line_4的一部分 而不是全局变量,因为全局变量可以任意的被其他函数所修改
# 缺点:如果要计算多条线上的y值,那么需要在调用的时候进行传递参数,麻烦

方法5

# 第5种:实例对象
class Line5(object):
    def __init__(self, k, b):
        self.k = k
        self.b = b

    def __call__(self, x):
        print(self.k * x + self.b)


line_5_1 = Line5(1, 2)
# 对象.方法()
# 对象()
line_5_1(0)
line_5_1(1)
line_5_1(2)
line_5_2 = Line5(11, 22)
line_5_2(0)
line_5_2(1)
line_5_2(2)
# 缺点:为了计算多条线上的y值,所以需要保存多个k, b的值,因此用了很多个实例对象, 浪费资源

关于__call__()的用法:
在这里插入图片描述
方法6

# 第6种:闭包
def line_6(k, b):
    def create_y(x):
        print(k*x+b)
    return create_y

line_6_1 = line_6(1, 2)
line_6_1(0)
line_6_1(1)
line_6_1(2)
line_6_2 = line_6(11, 22)
line_6_2(0)
line_6_2(1)
line_6_2(2)
  •  闭包,红色部分return的不能加(),不加()是函数引用,否则就应该是函数的返回值。
  •  在上面的例子中,函数line_6与变量k,b构成闭包,闭包具有提高代码可复用性的作用
  •  由于闭包引用了外部函数的局部变量,则外部函数的局部变量没有及时释放,消耗内存,但是相对于实例对象来说,已经好多了

在这里插入图片描述
思考:函数、匿名函数、闭包、对象 当做实参时 有什么区别?

  1. 匿名函数能够完成基本的简单功能,传递是这个函数的引用 只有功能。
  2. 普通函数能够完成较为复杂的功能,传递是这个函数的引用 只有功能。
  3. 闭包能够将较为复杂的功能,传递是这个闭包中的函数以及数据,因此传递是功能+数据。
  4. 对象能够完成最为复杂的功能,传递是很多数据+很多功能,因此传递是功能+数据。

修改外部函数中的变量

x = 300
def outer():
    x = 200
    def inner():
        nonlocal x
        # global x
        print('---1---x=%d' % x)
        x = 100
        print('---1---x=%d' % x)
    return inner

t1 = outer()
t1()

在这里插入图片描述

如果把nonlocal改成global,第一个x就变成300了。

1.闭包的条件:

  •  在外部函数定义一个内部函数
  •  外部函数必须有返回值
  •  外部函数的返回值为内部函数
  •  内部函数必须引用外部函数的成员

2.格式:

def 外部函数:
	外部函数的局部变量
	....
	def 内部函数:
		一系列操作,必须引用外部函数的变量
		....
	return 内部函数

3.闭包的作用

  •  保存返回闭包时的状态(外层函数变量的状态), 不存在多次传递参数导致覆盖的问题
def wai(a, b):
	c = 99
	def nei()
		s = a + b  + c
		print(s)
	return nei

func1 = wai(1, 2)
func2 = wai(3, 4)

# 无论是何种调用顺序,都会输出正确的结果,不会受到第二次传递的参数影响
# 相当于内部保存了每次传递参数在内部函数计算后的结果状态,不存在多次传递参数导致覆盖的问题

# 当执行func1 = wai(1, 2)时,a = 1, b = 2,内部函数nei在返回前已经保存了本次a,b的值

# 当执行func2 = wai(3, 4)时,a = 3, b = 4,内部函数nei在返回前已经保存了本次a,b的值

# 所以不会存在第二次传递3,4把第一次传递1,2的结果覆盖的情况

# func1()
# func2()

func2()
func1()

原理:对于每次调用外部函数,程序都会在内存中开辟空间,多次调用,每次的内存地址是不同的,而传递的参数都保存在对应的内存地址处,所以起到保存状态的作用

  •  可以使用同级作用域
  •  读取其他元素的内部变量
  •  可以延长作用域
  •  完成类似类的功能,简化代码,方便阅读,为装饰器做铺垫

4.闭包的缺点

  •  局部变量一直不回收会消耗内存
  •  作用域不直观

二、装饰器

1、概念

装饰器实现了开放封闭的代码原则, 原有的代码不发生修改又能增加新的功能 ,从本质上讲是将函数作为对象进行参数传递,并且作为对象进行返回

2.例

我们从一个简单的例子入手:

def set_func(func):
    def call_func():
        print("---这是权限验证1----")
        print("---这是权限验证2----")
        func()
    return call_func

# 下面就相当于test1 = set_func(test1)
@set_func
def test1():
    print("-----test1----")


test1()

运行结果:
在这里插入图片描述

  •  这里就相当于是实现了一个代理,我们重新执行的test1()实际上是代理。
  •  我们所营造出来的一种效果就是,test1()加不加@set_func,方法执行的方式是不变的。

@set_func的含义是:

set_func(test1)

我们可以用任何一个方式来接收,比如:

ret = set_func(test1)

ret()

这样执行结果没有变化,但是感觉不协调,所以我们用test1来接收,即

test1 = set_func(test1)

test1()
  •  就是把test1重新指向了call_func()函数。
  •  我们可以看到,set_func函数里面就是闭包。

一个装饰器可以对多个函数进行装饰

def set_func(func):
    def call_func(a):
        print("---这是权限验证1----")
        print("---这是权限验证2----")
        func(a)
    return call_func


@set_func  # 相当于 test1 = set_func(test1)
def test1(num):
    print("-----test1----%d" % num)


@set_func  # 相当于 test2 = set_func(test2)
def test2(num):
    print("-----test2----%d" % num)


test1(100)
test2(200)

运行结果:
在这里插入图片描述

装饰器在没有调用函数之前已经装饰了

  •  在调用前会先把被装饰函数作为参数执行加载装饰器函数
  •  执行装饰器肯定会返回一个内部函数地址
  •  再把返回值赋值给被装饰函数
  •  所以装饰前后被装饰函数的地址已经发生改变,即被装饰函数变为内部函数
def set_func(func):
    print("---开始进行装饰")
    def call_func(a):
        print("---这是权限验证1----")
        print("---这是权限验证2----")
        func(a)
    return call_func


@set_func  # 相当于 test1 = set_func(test1)
def test1(num):
    print("-----test1----%d" % num)


@set_func  # 相当于 test2 = set_func(test2)
def test2(num):
    print("-----test2----%d" % num)

# 装饰器在调用函数之前,已经被python解释器执行了,所以要牢记 当调用函数之前 其实已经装饰好了,尽管调用就可以了
# test1(100)
# test2(200)

运行结果:
在这里插入图片描述

对不定长参数的函数进行装饰

def set_func(func):
    print("---开始进行装饰")
    def call_func(*args, **kwargs): # 这里是形参,相当于定义,前面接收元组多个,后面接收字典多个
        print("---这是权限验证1----")
        print("---这是权限验证2----")
        # func(args, kwargs)  # 不行,相当于传递了2个参数 :1个元组,1个字典
        func(*args, **kwargs)  # 这里是实参,拆包,对应拆元组和拆字典
    return call_func


@set_func  # 相当于 test1 = set_func(test1)
def test1(num, *args, **kwargs):
    print("-----test1----%d" % num)
    print("-----test1----" , args)
    print("-----test1----" , kwargs)


test1(100)
test1(100, 200)
test1(100, 200, 300, mm=100)

运行结果:
在这里插入图片描述

对带有返回值的函数进行装饰(装饰模板)

def set_func(func):
    print("---开始进行装饰")
    def call_func(*args, **kwargs):
        print("---这是权限验证1----")
        print("---这是权限验证2----")
        # func(args, kwargs)  # 不行,相当于传递了2个参数 :1个元组,1个字典
        return func(*args, **kwargs)  # 拆包
    return call_func


@set_func  # 相当于 test1 = set_func(test1)
def test1(num, *args, **kwargs):
    print("-----test1----%d" % num)
    print("-----test1----" , args)
    print("-----test1----" , kwargs)
    return "ok"


@set_func
def test2():
    pass

ret = test1(100)
print(ret)

ret = test2()
print(ret)

运行结果:
在这里插入图片描述

多个装饰器对同一个函数进行装饰

def add_qx(func):
    print("---开始进行装饰权限1的功能---")
    def call_func(*args, **kwargs):
        print("---这是权限验证1----")
        return func(*args, **kwargs)
    return call_func


def add_xx(func):
    print("---开始进行装饰xxx的功能---")
    def call_func(*args, **kwargs):
        print("---这是xxx的功能----")
        return func(*args, **kwargs)
    return call_func


@add_qx
@add_xx
def test1():
    print("------test1------")


test1()

运行结果:
在这里插入图片描述

这就跟包裹一样,越上面的越先执行。至于开始装饰为什么先执行的xx,是因为@add_qx找下面,发现不是函数,而@add_xx下面才是函数,所以开始进行装饰的顺序反过来了。

举例:

def set_func_1(func):
    def call_func():
        # "<h1>haha</h1>"
        return "<h1>" + func() + "</h1>"
    return call_func

def set_func_2(func):
    def call_func():
        return "<td>" + func() + "</td>"
    return call_func


@set_func_1
@set_func_2
def get_str():
    return "haha"

print(get_str())

结果:

<h1><td>haha</td></h1>

使用类当做装饰器

class Test(object):
    def __init__(self, func):
        self.func = func

    def __call__(self):
        print("这里是装饰器添加的功能.....")
        return self.func()


@Test  # 相当于get_str = Test(get_str)
def get_str():
    return "haha"

print(get_str())

运行结果:
在这里插入图片描述

装饰器带参数

这种情况需要在原有的外面,再次嵌套一层,作为外部变量传参。
在这里插入图片描述
运行结果:
在这里插入图片描述

0

评论区