python装饰器原理源码示例分析

目录
  • 前言
  • 一、什么是装饰器
  • 二、为什么要用装饰器
  • 三、简单的装饰器
  • 四、装饰器的语法糖
  • 五、装饰器传参
  • 六、带参数的装饰器
  • 七、类装饰器
  • 八、带参数的类装饰器
  • 九、装饰器的顺序

前言

最近有人问我装饰器是什么,我就跟他说,其实就是装饰器就是类似于女孩子的发卡。你喜欢的一个女孩子,她可以有很多个发卡,而当她戴上不同的发卡,她的头顶上就是装饰了不同的发卡。但是你喜欢的女孩子还是你喜欢的女孩子。如果还觉得不理解的话,装饰器就是咱们的手机壳,你尽管套上了手机壳,但并不影响你的手机功能,可你的手机还是该可以给你玩,该打电话打电话,该玩游戏玩游戏,该收藏攻城狮白玉的博客就收藏攻城狮白玉的博客。而你的手机就变成了带手机壳的手机。

装饰器就是python的一个拦路虎,你干或者不干它,它都在那里。如果你想学会高级的python用法,装饰器就是你这个武松必须打倒的一只虎

本文的环境如下:

win10

python3.7

一、什么是装饰器

装饰器是给现有的模块增添新的小功能,可以对原函数进行功能扩展,而且还不需要修改原函数的内容,也不需要修改原函数的调用。

装饰器的使用符合了面向对象编程的开放封闭原则。

开放封闭原则主要体现在两个方面:

对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。

对修改封闭,意味着类一旦设计完成,就可以独立其工作,而不要对类尽任何修改。

二、为什么要用装饰器

使用装饰器之前,我们要知道,其实python里是万物皆对象,也就是万物都可传参。

函数也可以作为函数的参数进行传递的。

通过下面这个简单的例子可以更直观知道函数名是如何直接作为参数进行传递

def baiyu():
    print("我是攻城狮白玉")
def blog(name):
    print('进入blog函数')
    name()
    print('我的博客是 https://blog.csdn.net/zhh763984017')
if __name__ == '__main__':
    func = baiyu  # 这里是把baiyu这个函数名赋值给变量func
    func()  # 执行func函数
    print('------------')
    blog(baiyu)  # 把baiyu这个函数作为参数传递给blog函数

执行结果如下所示:

接下来,我想知道这baiyublog两个函数分别的执行时间是多少,我就把代码修改如下:

import time
def baiyu():
    t1 = time.time()
    print("我是攻城狮白玉")
    time.sleep(2)
    print("执行时间为:", time.time() - t1)
def blog(name):
    t1 = time.time()
    print('进入blog函数')
    name()
    print('我的博客是 https://blog.csdn.net/zhh763984017')
    print("执行时间为:", time.time() - t1)
if __name__ == '__main__':
    func = baiyu  # 这里是把baiyu这个函数名赋值给变量func
    func()  # 执行func函数
    print('------------')
    blog(baiyu)  # 把baiyu这个函数作为参数传递给blog函数

上述的改写已经实现了我需要的功能,但是,当我有另外一个新的函数【python_blog_list】,具体如下:

def python_blog_list():
    print('''【Python】爬虫实战,零基础初试爬虫下载图片(附源码和分析过程)
    https://blog.csdn.net/zhh763984017/article/details/119063252 ''')
    print('''【Python】除了多线程和多进程,你还要会协程
    https://blog.csdn.net/zhh763984017/article/details/118958668 ''')
    print('''【Python】爬虫提速小技巧,多线程与多进程(附源码示例)
    https://blog.csdn.net/zhh763984017/article/details/118773313 ''')
    print('''【Python】爬虫解析利器Xpath,由浅入深快速掌握(附源码例子)
    https://blog.csdn.net/zhh763984017/article/details/118634945 ''')

也需要计算函数执行时间的,那按之前的逻辑,就是改写如下:

def python_blog_list():
    t1 = time.time()
    print('''【Python】爬虫实战,零基础初试爬虫下载图片(附源码和分析过程)
    https://blog.csdn.net/zhh763984017/article/details/119063252 ''')
    print('''【Python】除了多线程和多进程,你还要会协程
    https://blog.csdn.net/zhh763984017/article/details/118958668 ''')
    print('''【Python】爬虫提速小技巧,多线程与多进程(附源码示例)
    https://blog.csdn.net/zhh763984017/article/details/118773313 ''')
    print('''【Python】爬虫解析利器Xpath,由浅入深快速掌握(附源码例子)
    https://blog.csdn.net/zhh763984017/article/details/118634945 ''')
    print("执行时间为:", time.time() - t1)

如果也要这样子写的话,不就重复造轮子了吗?虽说人类的本质是鸽王和复读机,但作为一个优秀的cv攻城狮(ctrl+c和ctrl+v)肯定是要想办法偷懒的呀

装饰器,就是可以让我们拓展一些原有函数没有的功能。

三、简单的装饰器

基于上面的函数执行时间的需求,我们就手写一个简单的装饰器进行实现。

import time
def baiyu():
    print("我是攻城狮白玉")
    time.sleep(2)
def count_time(func):
    def wrapper():
        t1 = time.time()
        func()
        print("执行时间为:", time.time() - t1)
    return wrapper
if __name__ == '__main__':
    baiyu = count_time(baiyu)  # 因为装饰器 count_time(baiyu) 返回的时函数对象 wrapper,这条语句相当于  baiyu = wrapper
    baiyu()  # 执行baiyu()就相当于执行wrapper()

这里的count_time是一个装饰器,装饰器函数里面定义一个wrapper函数,把func这个函数当作参数传入,函数实现的功能是把func包裹起来,并且返回wrapper函数。wrapper函数体就是要实现装饰器的内容。

当然,这里的wrapper函数名是可以自定义的,只要你定义的函数名,跟你return的函数名是相同的就好了

四、装饰器的语法糖

你如果看过其他python项目里面的代码里,难免会看到@符号,这个@符号就是装饰器的语法糖。因此上面简单的装饰器还是可以通过语法糖来实现的,这样就可以省去

baiyu = count_time(baiyu)

这一句代码,而直接调用baiyu()这个函数

换句话说,其实使用装饰器的是,默认传入的参数就是被装饰的函数。

import time
def count_time(func):
    def wrapper():
        t1 = time.time()
        func()
        print("执行时间为:", time.time() - t1)
    return wrapper
@count_time
def baiyu():
    print("我是攻城狮白玉")
    time.sleep(2)
if __name__ == '__main__':
    # baiyu = count_time(baiyu)  # 因为装饰器 count_time(baiyu) 返回的时函数对象 wrapper,这条语句相当于  baiyu = wrapper
    # baiyu()  # 执行baiyu()就相当于执行wrapper()
     baiyu()  # 用语法糖之后,就可以直接调用该函数了

五、装饰器传参

当我们的被装饰的函数是带参数的,此时要怎么写装饰器呢?

上面我们有定义了一个blog函数是带参数的

def blog(name):
    print('进入blog函数')
    name()
    print('我的博客是 https://blog.csdn.net/zhh763984017')

此时我们的装饰器函数要优化一下下,修改成为可以接受任意参数的装饰器

def count_time(func):
    def wrapper(*args,**kwargs):
        t1 = time.time()
        func(*args,**kwargs)
        print("执行时间为:", time.time() - t1)
    return wrapper

此处,我们的wrapper函数的参数为*args和**kwargs,表示可以接受任意参数

这时我们就可以调用我们的装饰器了。

import time
def count_time(func):
    def wrapper(*args, **kwargs):
        t1 = time.time()
        func(*args, **kwargs)
        print("执行时间为:", time.time() - t1)
     return wrapper
@count_time
def blog(name):
    print('进入blog函数')
    name()
    print('我的博客是 https://blog.csdn.net/zhh763984017')  if __name__ == '__main__':
    # baiyu = count_time(baiyu)  # 因为装饰器 count_time(baiyu) 返回的时函数对象 wrapper,这条语句相当于  baiyu = wrapper
    # baiyu()  # 执行baiyu()就相当于执行wrapper()

    # baiyu()  # 用语法糖之后,就可以直接调用该函数了
    blog(baiyu)

六、带参数的装饰器

前面咱们知道,装饰器函数也是函数,既然是函数,那么就可以进行参数传递,咱们怎么写一个带参数的装饰器呢?

前面咱们的装饰器只是实现了一个计数,那么我想在使用该装饰器的时候,传入一些备注的msg信息,怎么办呢?咱们一起看一下下面的代码

import time
def count_time_args(msg=None):
    def count_time(func):
        def wrapper(*args, **kwargs):
            t1 = time.time()
            func(*args, **kwargs)
            print(f"[{msg}]执行时间为:", time.time() - t1)
         return wrapper
    return count_time
@count_time_args(msg="baiyu")
def fun_one():
    time.sleep(1)
@count_time_args(msg="zhh")
def fun_two():
    time.sleep(1)
@count_time_args(msg="mylove")
def fun_three():
    time.sleep(1)
if __name__ == '__main__':
    fun_one()
    fun_two()
    fun_three()

咱们基于原来的count_time函数外部再包一层用于接收参数的count_time_args,接收回来的参数就可以直接在内部的函数里面调用了。上述代码执行效果如下:

七、类装饰器

上面咱们一起学习了怎么写装饰器函数,在python中,其实也可以同类来实现装饰器的功能,称之为类装饰器。类装饰器的实现是调用了类里面的__call__函数。类装饰器的写法比我们装饰器函数的写法更加简单。

当我们将类作为一个装饰器,工作流程:

  • 通过__init__()方法初始化类
  • 通过__call__()方法调用真正的装饰方法
import time
class BaiyuDecorator:
    def __init__(self, func):
        self.func = func
        print("执行类的__init__方法")
    def __call__(self, *args, **kwargs):
        print('进入__call__函数')
        t1 = time.time()
        self.func(*args, **kwargs)
        print("执行时间为:", time.time() - t1)
@BaiyuDecorator
def baiyu():
    print("我是攻城狮白玉")
    time.sleep(2)
def python_blog_list():
    time.sleep(5)
    print('''【Python】爬虫实战,零基础初试爬虫下载图片(附源码和分析过程)
    https://blog.csdn.net/zhh763984017/article/details/119063252 ''')
    print('''【Python】除了多线程和多进程,你还要会协程
    https://blog.csdn.net/zhh763984017/article/details/118958668 ''')
    print('''【Python】爬虫提速小技巧,多线程与多进程(附源码示例)
    https://blog.csdn.net/zhh763984017/article/details/118773313 ''')
    print('''【Python】爬虫解析利器Xpath,由浅入深快速掌握(附源码例子)
    https://blog.csdn.net/zhh763984017/article/details/118634945 ''')
@BaiyuDecorator
def blog(name):
    print('进入blog函数')
    name()
    print('我的博客是 https://blog.csdn.net/zhh763984017')
if __name__ == '__main__':
    baiyu()
    print('--------------')
    blog(python_blog_list)

八、带参数的类装饰器

当装饰器有参数的时候,__init__() 函数就不能传入func(func代表要装饰的函数)了,而func是在__call__函数调用的时候传入的。

class BaiyuDecorator:
    def __init__(self, arg1, arg2):  # init()方法里面的参数都是装饰器的参数
        print('执行类Decorator的__init__()方法')
        self.arg1 = arg1
        self.arg2 = arg2
     def __call__(self, func):  # 因为装饰器带了参数,所以接收传入函数变量的位置是这里
        print('执行类Decorator的__call__()方法')
        def baiyu_warp(*args):  # 这里装饰器的函数名字可以随便命名,只要跟return的函数名相同即可
            print('执行wrap()')
            print('装饰器参数:', self.arg1, self.arg2)
            print('执行' + func.__name__ + '()')
            func(*args)
            print(func.__name__ + '()执行完毕')
        return baiyu_warp
@BaiyuDecorator('Hello', 'Baiyu')
def example(a1, a2, a3):
    print('传入example()的参数:', a1, a2, a3)
if __name__ == '__main__':
    print('准备调用example()')
    example('Baiyu', 'Happy', 'Coder')
    print('测试代码执行完毕')

建议各位同学好好比对一下这里的代码和不带参数的装饰器代码的区别,加深理解。

九、装饰器的顺序

一个函数可以被多个装饰器进行装饰,那么装饰器的执行顺序是怎么样的呢?咱们执行一下下面的代码就清楚了

def BaiyuDecorator_1(func):
    def wrapper(*args, **kwargs):
        func(*args, **kwargs)
        print('我是装饰器1')
    return wrapper
def BaiyuDecorator_2(func):
    def wrapper(*args, **kwargs):
        func(*args, **kwargs)
        print('我是装饰器2')
     return wrapper
def BaiyuDecorator_3(func):
    def wrapper(*args, **kwargs):
        func(*args, **kwargs)
        print('我是装饰器3')
    return wrapper
@BaiyuDecorator_1
@BaiyuDecorator_2
@BaiyuDecorator_3
def baiyu():
    print("我是攻城狮白玉")
if __name__ == '__main__':
    baiyu()

由输出结果可知,在装饰器修饰完的函数,在执行的时候先执行原函数的功能,然后再由里到外依次执行装饰器的内容。

我们带三个装饰器的函数的代码如下:

@BaiyuDecorator_1
@BaiyuDecorator_2
@BaiyuDecorator_3
def baiyu():
    print("我是攻城狮白玉")

上述的代码可以看作如下代码,就能理解为何是由里到外执行了

baiyu = BaiyuDecorator_1 (BaiyuDecorator_2 (BaiyuDecorator_3(baiyu)))

以上就是python装饰器原理源码示例分析的详细内容,更多关于python装饰器原理的资料请关注我们其它相关文章!

(0)

相关推荐

  • 通俗易懂了解Python装饰器原理

    作用 装饰器可以用于用于装饰一个函数或方法,使得在不修改原函数.方法代码的前提下,为方法添加前置或后置操作: 例如突然想要计算一下各个函数的执行时间,又不希望在每一个函数中添加tim.time()来计算执行时间 用法 装饰器的写法网上很多,但是我觉得还是尽量先理解,再知道怎么写会比较好,所以会先说如何理解,在后面重写用法 实现 了解装饰器是如何实现的,远比会写装饰器更重要,简单的说装饰器就是接收一个函数对象,然后先执行前置操作,再执行函数,再执行后置操作: 这么说可能有些抽象,或者举一个不那么恰

  • Python装饰器原理与用法分析

    本文实例讲述了Python装饰器原理与用法.分享给大家供大家参考,具体如下: 1.装饰器的本质是函数,主要用来装饰其他函数,也就是为其他函数添加附加功能 2.装饰器的原则: (1) 装饰器不能修改被装饰的函数的源代码 (2) 装饰器不能修改被装饰的函数的调用方式 3.实现装饰器的知识储备 (1) Python中函数即'变量' a.变量在Python中的存储 x='Tomwenxing' y=x [说明]: 当Python解释器遇到语句x='Tomwenxing'时,它主要完成了两样工作: 1.在

  • python装饰器的特性原理详解

    这篇文章主要介绍了python装饰器的特性原理详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 今天发现了装饰器的另一种用法,下面就先上代码: data_list = [] def data_item(func): data_list.append(func) return func @data_item def foo(): return 1 @data_item def foo1(): return 2 @data_item def fo

  • python装饰器原理与用法深入详解

    本文实例讲述了python装饰器原理与用法.分享给大家供大家参考,具体如下: 你会Python嘛? 我会! 那你给我讲下Python装饰器吧! Python装饰器啊?我没用过哎 以上是我一个哥们面试时候发生的真实对白. ----------------------------------------------分割线------------------------------------------------------------------------------ 简言之,python装饰

  • python装饰器原理源码示例分析

    目录 前言 一.什么是装饰器 二.为什么要用装饰器 三.简单的装饰器 四.装饰器的语法糖 五.装饰器传参 六.带参数的装饰器 七.类装饰器 八.带参数的类装饰器 九.装饰器的顺序 前言 最近有人问我装饰器是什么,我就跟他说,其实就是装饰器就是类似于女孩子的发卡.你喜欢的一个女孩子,她可以有很多个发卡,而当她戴上不同的发卡,她的头顶上就是装饰了不同的发卡.但是你喜欢的女孩子还是你喜欢的女孩子.如果还觉得不理解的话,装饰器就是咱们的手机壳,你尽管套上了手机壳,但并不影响你的手机功能,可你的手机还是该

  • Python装饰器的执行过程实例分析

    本文实例分析了Python装饰器的执行过程.分享给大家供大家参考,具体如下: 今天看到一句话:装饰器其实就是对闭包的使用,仔细想想,其实就是这回事,今天又看了下闭包,基本上算是弄明白了闭包的执行过程了.其实加上几句话以后就可以很容易的发现,思路给读者,最好自己总结一下,有助于理解.通过代码来说吧. 第一种,装饰器本身不传参数,相对来说过程相对简单的 #!/usr/bin/python #coding: utf-8 # 装饰器其实就是对闭包的使用 def dec(fun): print("call

  • Python装饰器模式定义与用法分析

    本文实例讲述了Python装饰器模式定义与用法.分享给大家供大家参考,具体如下: 装饰器模式定义:动态地给一个对象添加一些额外的职责. 在Python中Decorator mode可以按照像其它编程语言如C++, Java等的样子来实现,但是Python在应用装饰概念方面的能力上远不止于此,Python提供了一个语法和一个编程特性来加强这方面的功能. 首先需要了解一下Python中闭包的概念:如果在一个内部函数里,对在外部作用域(但不是在全局作用域)的变量进行引用,那么内部函数就被认为是闭包(c

  • Python装饰器原理与基本用法分析

    本文实例讲述了Python装饰器原理与基本用法.分享给大家供大家参考,具体如下: 装饰器: 意义:在不能改变原函数的源代码,和在不改变整个项目中原函数的调用方式的情况下,给函数添加新的功能 由于不允许改变函数的源代码,在忽略调用方式的情况下,我们可能会有以下结果: def decorator(func): func() print("logging") def test1(): print("test1") def test2(): print("Test

  • Python装饰器用法示例小结

    本文实例讲述了Python装饰器用法.分享给大家供大家参考,具体如下: 下面的程序示例了python装饰器的使用: 示例一: def outer(fun): print fun def wrapper(arg): result=fun(arg) print 'over!' return result return wrapper @outer def func1(arg): print 'func1',arg return 'very good!' response=func1('python'

  • Python装饰器语法糖

    Python装饰器语法糖代码示例 ####装饰器的固定格式 ##普通版本 def timer(func): def inner(*args,**kwargs): '''执行函数之前要做的''' ret = func(*args,**kwargs) '''执行函数之后要做的''' return ret return inner ##wraps版本 from functools import wraps def deco(func): @wraps(func) #加在最内层函数正上方 def wra

  • Python 装饰器常用的创建方式及源码示例解析

    目录 装饰器简介 基础通用装饰器 源码示例 执行结果 带参数装饰器 源码示例 源码结果 源码解析 多装饰器执行顺序 源码示例 执行结果 解析 类装饰器 源码示例 执行结果 解析 装饰器简介 装饰器(decorator)是一种高级Python语法.可以对一个函数.方法或者类进行加工.在Python中,我们有多种方法对函数和类进行加工,相对于其它方式,装饰器语法简单,代码可读性高.因此,装饰器在Python项目中有广泛的应用.修饰器经常被用于有切面需求的场景,较为经典的有插入日志.性能测试.事务处理

  • 深入浅出分析Python装饰器用法

    本文实例讲述了Python装饰器用法.分享给大家供大家参考,具体如下: 用类作为装饰器 示例一 最初代码: class bol(object): def __init__(self, func): self.func = func def __call__(self): return "<b>{}</b>".format(self.func()) class ita(object): def __init__(self, func): self.func = f

  • spring-session简介及实现原理源码分析

    一:spring-session介绍 1.简介 session一直都是我们做集群时需要解决的一个难题,过去我们可以从serlvet容器上解决,比如开源servlet容器-tomcat提供的tomcat-redis-session-manager.memcached-session-manager. 或者通过nginx之类的负载均衡做ip_hash,路由到特定的服务器上.. 但是这两种办法都存在弊端. spring-session是spring旗下的一个项目,把servlet容器实现的httpSe

随机推荐