欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

【第十篇】闭包和装饰器

程序员文章站 2022-04-15 15:15:32
一、闭包 1.1 闭包的定义 在函数嵌套的前提下,内部函数使用了外部函数的变量,并且外部函数返回了内部函数,我们把这个使用外部函数变量的内部函数称为闭包。 1.2 闭包的构成 构成条件: 在函数嵌套(函数里面再定义函数)的前提下 内部函数使用了外部函数的变量(还包括外部函数的参数) 外部函数返回了内 ......

一、闭包

1.1 闭包的定义

在函数嵌套的前提下,内部函数使用了外部函数的变量,并且外部函数返回了内部函数,我们把这个使用外部函数变量的内部函数称为闭包。

1.2 闭包的构成

构成条件:

  • 在函数嵌套(函数里面再定义函数)的前提下
  • 内部函数使用了外部函数的变量(还包括外部函数的参数)
  • 外部函数返回了内部函数
 1 # 定义一个外部函数
 2 def func_out(num1):
 3     # 定义一个内部函数
 4     def func_inner(num2):
 5         # 内部函数使用了外部函数的变量(num1)
 6         result = num1 + num2
 7         print("结果是:", result)
 8     # 外部函数返回了内部函数,这里返回的内部函数就是闭包
 9     return func_inner
10 
11 
12 # 创建闭包实例
13 f = func_out(1)
14 # 执行闭包
15 f(2)
16 f(3)
17 
18 # 执行结果
19 # 结果是: 3
20 # 结果是: 4

ps:通过上面的输出结果可以看出闭包保存了外部函数内的变量num1,每次执行闭包都是在num1 = 1 基础上进行计算。

闭包的作用:闭包可以保存外部函数内的变量,不会随着外部函数调用完而销毁。

注意:由于闭包引用了外部函数的变量,则外部函数的变量没有及时释放,消耗内存。

 1.3 __closure__

判断函数是否是闭包函数:输出的__closure__有cell元素 :是闭包函数

 1 def func():
 2     name = 'eva'
 3 
 4     def inner():
 5         print(name)
 6     print(inner.__closure__)    # (<cell at 0x0000028dd6fd0e58: str object at 0x0000028dd70d1f48>,) 
 7     return inner
 8 
 9 
10 f = func()
11 f()
12 
13 
14 
15 
16 name = 'egon'
17 
18 
19 def func2():
20     def inner():
21         print(name)
22     print(inner.__closure__)    # 结果为none,不是闭包函数,闭包函数是对外包作用域的引用,而非全局作用域
23     return inner
24 
25 
26 f2 = func2()
27 f2()

1.4 修改闭包中使用的外包变量

错误示例:

 1 # 定义一个外部函数
 2 def func_out(num1):
 3 
 4     # 定义一个内部函数
 5     def func_inner(num2):
 6         # 这里本意想要修改外部num1的值,实际上是在内部函数定义了一个局部变量num1
 7         num1 = 10
 8         # 内部函数使用了外部函数的变量(num1)
 9         result = num1 + num2
10         print("结果是:", result)
11 
12     print(num1)
13     func_inner(1)
14     print(num1)
15 
16     # 外部函数返回了内部函数,这里返回的内部函数就是闭包
17     return func_inner
18 
19 
20 # 创建闭包实例
21 f = func_out(1)
22 # 执行闭包
23 f(2)

正确的示例:

 1 # 定义一个外部函数
 2 def func_out(num1):
 3 
 4     # 定义一个内部函数
 5     def func_inner(num2):
 6         # 这里本意想要修改外部num1的值,实际上是在内部函数定义了一个局部变量num1
 7         nonlocal num1  # 告诉解释器,此处使用的是 外部变量a
 8         # 修改外部变量num1
 9         num1 += 10
10         # 内部函数使用了外部函数的变量(num1)
11         result = num1 + num2
12         print("结果是:", result)
13 
14     print(num1)
15     func_inner(1)
16     print(num1)
17 
18     # 外部函数返回了内部函数,这里返回的内部函数就是闭包
19     return func_inner
20 
21 
22 # 创建闭包实例
23 f = func_out(1)
24 # 执行闭包
25 f(2)

1.5 闭包的嵌套

 1 def wrapper():
 2     money = 1000
 3     def func():
 4         name = 'eva'
 5         def inner():
 6             print(name, money)
 7         return inner
 8     return func
 9 
10 
11 f = wrapper()
12 i = f()
13 i()

二、装饰器

2.1 装饰器的定义

就是给已有函数增加额外功能的函数,它本质上就是一个闭包函数。

装饰器函数的特点:

  • 不修改已有函数的源代码
  • 不修改已有函数的调用方式
  • 给已有函数增加额外的功能

2.2 装饰器的示例

 1 # 登录验证
 2 def check(fn):
 3     def inner():
 4         print("登录。。。")
 5         fn()
 6     return inner
 7 
 8 
 9 def comment():
10     print("aa")
11 
12 
13 # 使用装饰器来装饰函数
14 comment = check(comment)
15 comment()

装饰器的基本雏形:

def wrapper(fn):  # fn:目标函数.
    def inner():
        '''执行函数之前'''
        fn() # 执行被装饰的函数
        '''执行函数之后'''
    return inner

说明:

  • 闭包函数有且只有一个参数,必须是函数类型,这样定义的函数才是装饰器
  • 写代码要遵循开放封闭原则,它规定已经实现的功能代码不允许被修改,但可以被扩展

2.3 语法糖

python给提供了一个装饰函数更加简单的写法,那就是语法糖,语法糖的书写格式是: @装饰器名字,通过语法糖的方式也可以完成对已有函数的装饰

import time


def timer(func):
    def inner():
        start = time.time()
        func()
        print(time.time() - start)
    return inner


@timer   #==> func1 = timer(func1)
def func1():
    print('in func1')


func1()

说明:

  • @timer 等价于func1 = timer(func1)
  • 装饰器的执行时间是加载模块时立即执行的

2.4 通用装饰器

2.4.1 装饰带有参数的函数

 1 # 添加输出日志的功能
 2 def logging(fn):
 3     
 4     def inner(num1, num2):
 5         print("--正在努力计算--")
 6         fn(num1, num2)
 7 
 8     return inner
 9 
10 
11 # 使用装饰器装饰函数
12 @logging
13 def sum_num(a, b):
14     result = a + b
15     print(result)
16 
17 
18 sum_num(1, 2)

2.4.2 装饰不定长参数的函数

 1 import time
 2 
 3 
 4 # 计算执行时间
 5 def timer(func):
 6     def inner(*args, **kwargs):
 7         start = time.time()
 8         re = func(*args, **kwargs)
 9         print(time.time() - start)
10         return re
11     return inner
12 
13 
14 # 使用语法糖装饰函数
15 @timer   # ==> func1 = timer(func1)
16 def func1(a, b):
17     print('in func1', a, b)
18 
19 
20 # 使用语法糖装饰函数
21 @timer   # ==> func2 = timer(func2)
22 def func2(a):
23     print('in func2 and get a:%s'%(a))
24     return 'fun2 over'
25 
26 
27 func1('aaaaaa', 'bbbbbb')
28 print(func2('aaaaaa'))

2.4.3 装饰带有返回值的函数

 1 import time
 2 
 3 
 4 def timer(func):
 5     def inner(*args, **kwargs):
 6         start = time.time()
 7         re = func(*args, **kwargs)
 8         print(time.time() - start)
 9         return re
10     return inner
11 
12 
13 @timer   # ==> func2 = timer(func2)
14 def func2(a):
15     print('in func2 and get a:%s' % a)
16     return 'fun2 over'
17 
18 
19 func2('aaaaaa')
20 print(func2('aaaaaa'))

2.4.4 装饰器的完善

上面的装饰器已经非常完美了,但是正常我们情况下查看函数的一些信息的方法在此处都会失效

1 def index():
2     '''这是一个主页信息'''
3     print('from index')
4 
5 
6 print(index.__doc__)    # 查看函数注释的方法
7 print(index.__name__)   # 查看函数名的方法

为了不让他们失效,我们还要在装饰器上加上一点来完善它:

 1 from functools import wraps
 2 
 3 
 4 def deco(func):
 5     @wraps(func)    # 加在最内层函数正上方
 6     def wrapper(*args, **kwargs):
 7         return func(*args, **kwargs)
 8     return wrapper
 9 
10 
11 @deco
12 def index():
13     '''哈哈哈哈'''
14     print('from index')
15 
16 
17 print(index.__doc__)    # 哈哈哈哈
18 print(index.__name__)   # index

2.5 多个装饰器的使用

 1 def make_div(func):
 2     """对被装饰的函数的返回值 div标签"""
 3     def inner(*args, **kwargs):
 4         return "<div>" + func() + "</div>"
 5     return inner
 6 
 7 
 8 def make_p(func):
 9     """对被装饰的函数的返回值 p标签"""
10     def inner(*args, **kwargs):
11         return "<p>" + func() + "</p>"
12     return inner
13 
14 
15 # 装饰过程: 1 content = make_p(content) 2 content = make_div(content)
16 # content = make_div(make_p(content))
17 @make_div
18 @make_p
19 def content():
20     return "人生苦短"
21 
22 
23 result = content()
24 
25 print(result)

注:多个装饰器可以对函数进行多个功能的装饰,装饰顺序是由内到外的进行装饰

2.6 带有参数的装饰器

 带有参数的装饰器就是使用装饰器装饰函数的时候可以传入指定参数,语法格式: @装饰器(参数,...)

 1 # 添加输出日志的功能
 2 def logging(flag):
 3 
 4     def decorator(fn):
 5         def inner(num1, num2):
 6             if flag == "+":
 7                 print("--正在努力加法计算--")
 8             elif flag == "-":
 9                 print("--正在努力减法计算--")
10             result = fn(num1, num2)
11             return result
12         return inner
13 
14     # 返回装饰器
15     return decorator
16 
17 
18 # 使用装饰器装饰函数
19 @logging("+")
20 def add(a, b):
21     result = a + b
22     return result
23 
24 
25 @logging("-")
26 def sub(a, b):
27     result = a - b
28     return result
29 
30 
31 result = add(1, 2)
32 print(result)
33 
34 result = sub(1, 2)
35 print(result)

ps:类装饰器在面向对象章节讲解