百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术资源 > 正文

二十一、深入Python强大的装饰器

off999 2024-12-03 00:13 10 浏览 0 评论

「@Author: Runsen」

最近有同学在问关于Python中装饰器的问题,说不太理解装饰器的装饰过程。

那么在下面Runsen来给大家深入讲解一下装饰器的整个实现过程的。

闭包

想要理解Python中的装饰器,不得不先理解闭包(closure)这一概念。

闭包就应该想起了嵌套函数,也可以将闭包理解为一种特殊的函数,这种函数由两个函数的嵌套组成,外函数和内函数。

def 外层函数(参数):
    def 内层函数():
        print("内层函数执行", 参数)

    return 内层函数

内层函数的引用 = 外层函数("传入参数")
内层函数的引用()

在一个外函数中定义了一个内函数,内函数里运用了外函数的临时变量,并且外函数的返回值是内函数的引用。这样就构成了一个闭包。

下面举一个具体的闭包函数的实例,代码如下。

# outer是外部函数
def outer(a):
    # inner是内函数
    def inner( b ):
        #在内函数中 用到了外函数的临时变量
        print(a+b)
    # 外函数的返回值是内函数的引用
    return inner
ret = outer(5) #ret = inner
ret(10) #15 ret 存了外函数的返回值,也就是inner函数的引用,这里相当于执行inner函数

装饰器

装饰器,顾名思义,就是用来“装饰”的。比如@Runsen就是一个装饰器,其中"Runsen"是你的装饰器的名字。它能装饰的东西有:函数、类。装饰器一般在函数、类的上面用@符号定义。

装饰器本质上是一个Python函数(一定有参数),如果严格来说,装饰器只是语法糖,也可以将装饰器叫做一种特殊的闭包。

装饰器是可调用的对象,可以像常规的可调用对象那样调用,特殊的地方是装饰器的参数是一个函数名。

下面就是最简单的装饰器,代码来自Python3官方文档。

def warp(obj):
    return obj
        
@warp    # 等价于 foo = warp(foo)
def foo():    
    print('hello decorator!')
 
foo()    # => hello decorator!  

上面使用了装饰器的代码,其实我们可以通过其它方式达到相同的效果,具体见下。

def foo():
    print('hello decorator!')
 
foo = warp(foo)
foo()    # => hello decorator!

嵌套函数的装饰器

在上面代码中装饰器都只是一个普通的函数,如果该函数是嵌套函数,那么函数传入的参数,在内部函数依然可以使用。

先看两段代码,在这里my_decorator就是一个装饰器。

def my_decorator(func):
    def wrapper():
        print('wrapper of decorator')
        func()
    return wrapper

def greet():
    print('hello world')

greet = my_decorator(greet)
greet()

# 输出
wrapper of decorator
hello world

my_decorator函数传入greet函数名方法,中间有一个wrapper内函数方法, 而return wrapper说明要执行wrapper内函数,wrapper内函数,于是执行greet函数名方法。

其实,greet = my_decorator(greet)这个代码可以用装饰器来替代,在greet上面加一个@my_decorator,然后直接执行greet(),最终输出一样。

def my_decorator(func):
    def wrapper():
        print('wrapper of decorator')
        func()
    return wrapper

@my_decorator
def greet():
    print('hello world')

greet()

wrapper of decorator
hello world

装饰器就是继承了 my_decorator函数,因此先调用my_decorator中的wrapper打印出 wrapper of decorator,然后func()被调用,传入的参数是greet,因此指的就是greet(),所以在打印出hello world

带参数嵌套函数的装饰器

有时候嵌套函数需要传入参数到内部函数,这时候用*args, **kwargs接受就可以了。*args接收元组,**kwargs接受字典。

下面,我们来看一个例子。

# repeat重复输出,num指重复输出次数
def repeat(num):
    def my_decorator(func):
        def wrapper(*args, **kwargs):
            for i in range(num):
                print('wrapper of decorator')
                func(*args, **kwargs)
        return wrapper
    return my_decorator


@repeat(4)
def greet(message):
    print(message)

greet('hello world')

# 输出:
wrapper of decorator
hello world
wrapper of decorator
hello world
wrapper of decorator
hello world
wrapper of decorator
hello world

上面代码的意思:@repeat(4)将会执行greet(4),由于存在return my_decorator,所以下一步执行my_decorator(greet).由于又存在return wrapper。所以下一步将会执行wrapper(*args, **kwargs)。这里的*args, **kwargs指的是hello world字符串。因此最终打印四次wrapper of decoratorhello world

但是自定义参数的装饰器将改变函数本身的元信息,即函数不再是本身的函数

greet.__name__
## 输出
'wrapper'

这时,需要使用内置模块functools.wrap会保留原函数的元信息。

import functools

def repeat(num):
    def my_decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            for i in range(num):
                print('wrapper of decorator')
                func(*args, **kwargs)
        return wrapper
    return my_decorator

@repeat(4)
def greet(message):
    print(message)

greet.__name__

# 输出 不是wrapper
'greet' 

类装饰器

类装饰器主要依赖函数__call__ ,因此我们主要重写__call__即可。

每当调用一个类的实例,函数__call__就会执行一次。

下面,我们来看一个例子。

class Count:
    def __init__(self, func):
        self.func = func
        self.num_calls = 0

    def __call__(self, *args, **kwargs):
        self.num_calls += 1
        print('num of calls is: {}'.format(self.num_calls))
        return self.func(*args, **kwargs)

@Count
def example():
    print("hello world")

example()

# 输出
num of calls is: 1
hello world

example()

# 输出
num of calls is: 2
hello world

嵌套装饰器

我们可以把多个装饰器叠加在同一个函数上,这个就叫做嵌套装饰。

嵌套装饰器的顺序是从下到上,

@f2
@f1
def greet(name):
    print(f"Hello {name}")

所以上面的例子就是等价于:greet = f2(f1(greet))

那么相当于,从里到外。

import functools

def my_decorator1(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print('execute decorator1')
        func(*args, **kwargs)
    return wrapper


def my_decorator2(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print('execute decorator2')
        func(*args, **kwargs)
    return wrapper


@my_decorator1
@my_decorator2
def greet(message):
    print(message)

greet('hello world')  #相当于my_decorator1(my_decorator2(greet('hello world')))

# 输出
execute decorator1
execute decorator2
hello world

看完这篇文章还不理解装饰器,只有一种可能,说明我写的还不够清晰,那点赞鼓励鼓励我吧。

今天也学到了很多东西呢,明天有什么新知识呢?真期待鸭~如果喜欢文章可以关注我哦~

?

本文已收录 GitHub,传送门~[1] ,里面更有大厂面试完整考点,欢迎 Star。

?


Reference

[1]

传送门~: https://github.com/MaoliRUNsen/runsenlearnpy100

相关推荐

独家 | 5 个Python高级特性让你在不知不觉中成为Python高手

你已经使用Python编程了一段时间,编写脚本并解决各种问题。是你的水平出色吗?你可能只是在不知不觉中利用了Python的高级特性。从闭包(closure)到上下文管理器(contextmana...

Python装饰器

Python装饰器是一种用于修改函数或类的行为的特殊语法。它们允许在不修改原始代码的情况下,通过将函数或类作为参数传递给另一个函数来添加额外的功能。装饰器本质上是一个函数,它接受一个函数作为参数,并返...

中高阶Python常规用法--上下文管理器

Python以简单性和通用性著称,是一种深受全球开发人员喜爱的编程语言。它提供了大量的特性和功能,使编码成为一种愉快的体验。在这些功能中,一个经常被新手忽视的强大工具是上下文管理器。上下文管理器是高...

Python小案例67- 装饰器

Python装饰器是一种用于修改函数或类的行为的特殊语法。它们允许在不修改原始代码的情况下,通过将函数或类作为参数传递给另一个函数来添加额外的功能。装饰器本质上是一个函数,它接受一个函数作为参数,并返...

python常用的语法糖

概念Python的语法糖(SyntacticSugar)是指那些让代码更简洁、更易读的语法特性,它们本质上并不会增加新功能,但能让开发者更高效地编写代码。推导式写法推导式是Python最经典的...

python - 常用的装饰器 decorator 有哪些?

python编程中使用装饰器(decorator)工具,可以使代码更简洁清晰,提高代码的重用性,还可以为代码维护提供方便。对于python初学者来说,根据装饰器(decorator)的字面意思并不...

python数据缓存怎么搞 ?推荐一个三方包供你参考,非常简单好用。

1.数据缓存说明数据缓存可以说也是项目开发中比不可少的一个工具,像我们测试的系统中,你都会见到像Redis一样的数据缓存库。使用缓存数据库的好处不言而喻,那就是效率高,简单数据直接放在缓存中...

用于时间序列数据的Graphite监视工具

结合第三方工具,Graphite为IT性能监控提供了许多好处。本文介绍其核心组件,包括Carbon、Whisper以及安装的基本准则。Graphite监视工具可实时或按需,大规模地绘制来自多个来源的时...

Python3+pygame实现的坦克大战

一、显示效果二、代码1.说明几乎所有pygame游戏,基本都遵循一定的开发流程,大体如下:初始化pygame创建窗口while循环检测以及处理事件(鼠标点击、按键等)更新UI界面2.代码创建一个m...

Python之鸭子类型:一次搞懂with与上下文装饰器

引言在鸭子类型的理念的基础之上,从关注类型,转变到关注特性和行为。结合Python中的魔法函数的体系,我们可以将自定义的类型,像内置类型一样被使用。今天这篇文章中,接着该话题,继续聊一下with语法块...

Python必会的50个代码操作

学习Python时,掌握一些常用的程序操作非常重要。以下是50个Python必会的程序操作,主要包括基础语法、数据结构、函数和文件操作等。1.HelloWorldprint("Hello,...

一文掌握Python 中的同步和异步

同步代码(Sync)同步就像在一个流水线上工作,每个任务都等待前一个任务完成。示例:机器A切割钢板→完成后,机器B钻孔→完成后,机器C上色。在Python中,同步代码看起来像这样:im...

python 标注模块timeit: 测试函数的运行时间

在Python中,可以使用内置的timeit模块来测试函数的运行时间。timeit模块提供了一个简单的接口来测量小段代码的执行时间。以下是使用timeit测试函数运行时间的一般步骤:导入...

Python带你找回童年的万花尺

还记得小时候的万花尺吧?这么画:一点也不费脑筋,就可以出来这么多丰富多彩的复杂几何图形。具体而言,可以用万花尺玩具(如图2-1所示)来绘制数学曲线。这种玩具由两个不同尺寸的塑料齿轮组成,一大一小。小的...

Python 时间模块深度解析:从基础到高级的全面指南

直接上干货一、时间模块核心类介绍序号类名说明1datetime.datetime表示一个具体的日期和时间,结合了日期和时间的信息。2datetime.date表示一个具体的日期。3datetime.t...

取消回复欢迎 发表评论: