python 闭包,装饰器 详解

本文详细探讨了Python中的闭包和装饰器。首先解释了闭包的概念,包括函数引用、闭包的执行原理及如何在闭包中修改外部函数的变量。接着介绍了装饰器,阐述其功能,通过不同示例展示了装饰无参数、有参数、有返回值的函数,以及如何使用装饰器进行功能扩展、日志记录和权限验证等。文章还涵盖了装饰器的参数化和多个装饰器的使用顺序。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

python 闭包 装饰器

闭包

在python中一切皆对象,这样就使得变量所拥有的属性,函数也同样拥有。这样我们就可以理解在函数内创建一个函数的行为是完全合法的。这种函数被叫做内嵌函数,这种函数只可以在外部函数的作用域内被正常调用,在外部函数的作用域之外调用会报错.而如果内部函数里引用了外部函数里定义的对象(甚至是外层之外,但不是全局变量),那么此时内部函数就被称为闭包函数。闭包函数所引用的外部定义的变量被叫做自由变量。闭包从语法上看非常简单,但是却有强大的作用。闭包可以将其自己的代码和作用域以及外部函数的作用结合在一起

1. 函数引用

def test1():
    print("--- in test1 func----")
    
# 调⽤函数
test1()

# 引⽤函数
ret = test1
print(id(ret))
print(id(test1))

#通过引⽤调⽤函数
ret()

运行结果:

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

2.什么是闭包

  • 函数嵌套
  • 往往内函数使用外函数的自由变量
  • 外函数返回内函数的引用
# 定义一个函数 求 y = Kx + b 的值
# –*– coding: utf-8 –*–
# @Time      : 2019/2/23 20:34
# @Author    : Damon_duanlei
# @FileName  : bibao_test.py
# @BlogsAddr : https://blog.youkuaiyun.com/Damon_duanlei


def line(k, b):
    def creat_y(x):
        print(k * x + b)
    return creat_y


if __name__ == '__main__':
    line_a = line(6, 2)
    line_a(3)
    line_b = line(2, 5)
    line_b(5)

运行结果:

>>>
20
15
闭包执行原理:(上文代码为例)
  1. 代码从上向下执行,运行到第9行代码,发现定义了一个函数,系统不会进入函数执行,而是跳过此段函数代码继续向下执行第15行代码.
  2. 当系统运行到第 16 行代码, 发现此行代码调用上方定义的函数, 便带着参数 k = 6, b = 2跳到第 9 行执行函数.
  3. 外部函数(line)执行到第 10 行, 再次发现定义函数, 系统继续跳过定义函数阶段代码向下执行第12行 return 语句, 将内函数引用返回给第 16 行定义的变量.
  4. 系统向下执行第 17 行代码, 通过引用调用内函数 create_y ,执行过程中内函数未发现变量 k b 就会向上一层(外层)函数中寻找变量k和b , 找到后执行完内函数打印结果. 至此完成一个闭包过程
  5. 代码继续向下执行到第 18行, 如上流程在执行一次.
总结: 函数, 匿名函数, 闭包, 对象的区别

函数: 普通函数能够完成较为复杂的功能, 普通函数传递的是这个函数的引用,只有功能.

匿名函数: 匿名函数能够完成较为简单的功能, 匿名函数传递的是这个函数的引用,只有功能.

闭包: 闭包能够完成较为复杂的功能, 闭包传递的是这个比保重的函数以及数据,因此传递的事功能+数据

对象:对象能够完成最为复杂的功能,对象传递所有的属性和方法,但是占用较大资源

3 闭包结构中修改外部函数中的变量

类似于函数中修改全局变量使用globle ,在闭包中使用 nonlocal(python3) 可以修改外部函数中的变量

def counter(start=0):
    def incr():
        nonlocal start
        start += 1
        return start
	return incr
c1 = counter(5)
print(c1())
print(c1())
c2 = counter(50)
print(c2())
print(c2())
print(c1())
print(c1())
print(c2())
print(c2())

装饰器 (decorator)

装饰器的核心: 将函数 A 的引用当做实参,传递到闭包 B 的外层变量中去, 当调用闭包的内函数时, 通过传递的引用调用A函数.

变现形式: 在不改变原函数的情况下, 对原函数的功能进行修改

实质: 是一个函数

参数: 是要装饰的函数名

返回: 装饰后的函数名

作用: 为已经存在的对象添加额外的功能

特点: 不需要对对象做任何的代码上的变动

1.装饰器的功能

  1. 引入日志
  2. 函数执行时间统计
  3. 执行函数前预处理
  4. 执行函数后清理功能
  5. 权限校验
  6. 缓存

2. 装饰器示例

装饰无参数的函数
# –*– coding: utf-8 –*–
# @Time      : 2019/2/25 21:14
# @Author    : Damon_duanlei
# @FileName  : test02.py
# @BlogsAddr : https://blog.youkuaiyun.com/Damon_duanlei
import time


def timer(func):
    def call_func():
        t1 = time.time()
        func()
        t2 = time.time()
        print(t2 - t1)
    return call_func


@timer # 等价于 test = timer(test)
# 测试函数
def test():
    print("开始执行test:")
    for i in range(1000):
        for j in range(1000):
            pass
    print("test执行结束")


if __name__ == '__main__':
    test()

执行结果:

>>>
开始执行test:
test执行结束
0.014960050582885742

详解:

上面代码装饰器执行行为可理解成

test = timer(test)
# test 先作为参数赋值给 func 后, test 接收指向 timer 返回的 call_func  
test()
# 调用 test(), 即等价调用 call_func
# 内部函数 call_func 被引⽤,所以外部函数的func变量(⾃由变量)并没有释
# func 里保存的事原 test 函数对象
2. 被装饰的函数有参数
# –*– coding: utf-8 –*–
# @Time      : 2019/2/25 21:49
# @Author    : Damon_duanlei
# @FileName  : test03.py
# @BlogsAddr : https://blog.youkuaiyun.com/Damon_duanlei


def check(func):
    def call_func(user_name):
        print("校验权限1")
        print("校验权限2")
        func(user_name)
    return call_func


@check  # 等价于 test = timer(test)
# 测试函数
def test(name):
    print("{} 正在执行程序".format(name))


if __name__ == '__main__':
    test("臭臭")

3. 被装饰的函数有不定长参数
# –*– coding: utf-8 –*–
# @Time      : 2019/2/25 21:58
# @Author    : Damon_duanlei
# @FileName  : test04.py
# @BlogsAddr : https://blog.youkuaiyun.com/Damon_duanlei
import time
import random


def timer(func):
    def call_func(*args, **kwargs):
        t1 = time.time()
        func(*args, **kwargs)
        t2 = time.time()
        print(t2 - t1)
    return call_func


@timer  # 等价于 test = timer(test)
# 测试函数
def bubble_sort(sort_list, reverse):
    if reverse == 1:
        for i in range(len(sort_list) - 1):
            for j in range(len(sort_list) - 1 - i):
                if sort_list[j] > sort_list[j + 1]:
                    sort_list[j], sort_list[j + 1] = sort_list[j + 1], sort_list[j]
    else:
        for i in range(len(sort_list) - 1):
            for j in range(len(sort_list) - 1 - i):
                if sort_list[j] < sort_list[j + 1]:
                    sort_list[j], sort_list[j + 1] = sort_list[j + 1], sort_list[j]


if __name__ == '__main__':
    sort_list = [random.randint(0, 10000) for i in range(10000)]
    bubble_sort(sort_list, 2)

4. 装饰有 return 的函数
# –*– coding: utf-8 –*–
# @Time      : 2019/2/25 21:58
# @Author    : Damon_duanlei
# @FileName  : test04.py
# @BlogsAddr : https://blog.youkuaiyun.com/Damon_duanlei
import time
import random


def timer(func):
    def call_func(*args, **kwargs):
        t1 = time.time()
        number = func(*args, **kwargs)
        t2 = time.time()
        print("排序用时:{}".format(t2 - t1))
        return number

    return call_func


@timer  # 等价于 test = timer(test)
# 测试函数
def test(num_a, num_b):
    return num_a + num_b


if __name__ == '__main__':
    num = test(11, 22)
    print(num)

注意 : 一般情况下为了让装饰器更通用, 可以在内函数调用传递进来的装饰函数前加 return

5. 装饰器本身带参数, 在原有装饰器的基础上, 设置外部变量
from time import ctime, sleep
def timefun_arg(pre="hello"):
	def timefun(func):
		def wrapped_func():
			print("%s called at %s %s" % (func.__name__, 			ctime(), pre))
			return func()
		return wrapped_func
	return timefun
# 下⾯的装饰过程
# 1. 调⽤timefun_arg("chouchou")
# 2. 将步骤1得到的返回值,即time_fun返回, 然后time_fun(foo)
# 3. 将time_fun(foo)的结果返回,即wrapped_func
# 4. 让foo = wrapped_fun,即foo现在指向wrapped_func
@timefun_arg("chouchou")
def foo():
print("I am foo")
@timefun_arg("python")
def too():
print("I am too")
foo()
sleep(2)
foo()
too()
sleep(2)
too()
6 一个装饰器同时装饰多个函数.

装饰器在被装饰函数调用前就已经开始装饰, 装饰器每装饰一个函数, 就创建一个闭包

7 多个装饰器装饰同一个函数
# –*– coding: utf-8 –*–
# @Time      : 2019/2/26 22:40
# @Author    : Damon_duanlei
# @FileName  : test05.py
# @BlogsAddr : https://blog.youkuaiyun.com/Damon_duanlei


def add_first(func):
    print("开始装饰first功能")

    def inner():
        print("这是first的功能")
        func()

    return inner


def add_second(func):
    print("开始装饰second功能")

    def inner():
        print("这是second的功能")
        func()

    return inner


@add_second
@add_first
def test():
    print("------test------")


if __name__ == '__main__':
    test()

运行结果:

>>>
开始装饰first功能
开始装饰second功能
这是second的功能
这是first的功能
------test------

结论: 如果多的装饰器装饰同一个函数, 离被装饰函数近的装饰器先开始装饰, 但执行顺序与装饰顺序相反.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值