什么是Python装饰器

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了什么是Python装饰器相关的知识,希望对你有一定的参考价值。

装饰器(decorator)是Python中的高级语法。装饰的意思就是动态扩展被装饰对象的功能。装饰器可以用于装饰函数、方法和类。
一 嵌套函数
# 定义一个外层函数def foo(): # 定义了一个内部函数 def bar(): print("hello world")
函数bar是一个定义在foo函数内部的函数。
Python中的函数是支持嵌套的,也就是可以在一个函数内部再定义一个函数。
然后,我们还知道函数是可以当作变量的,于是我们就可以在foo函数中把定义的这个bar函数返回。就像下面这样:
# 定义一个外层函数def foo(): # 定义了一个内层函数 def bar(): print("hello world") return
barfunc = foo()func() # func --> bar,这里执行func其实就相当于执行了在foo函数内部定义的bar函数
二 闭包形态1
# 闭包形态1def foo(): name = "Andy" # 外部函数的局部变量 # 定义了一个内部函数 def bar():
print(name) # 虽然bar函数中没有定义name变量,但是它可以访问外部函数的局部变量name return barfunc =
foo()func() # func --> bar --> 除了是一个函数,还包含一个值(它外层函数的局部变量)的引用
三 闭包形态2
# 闭包形态2def foo(name): # 给一个函数传参也相当于给函数定义了一个局部变量 # 定义了一个内部函数 def bar():
print(name) # 内部函数同样可以获取到传到外部函数的变量(参数) return barfunc = foo("Andy") #
把“Andy”当成参数传入foo函数 --> 其内部定义的bar函数也能拿到这个“Andy”func() # func --> bar -->
除了是一个函数,还包含一个值(它外层函数的参数)的引用
四 装饰器形态1
# 还是定义一个外层函数def foo(name): # 我接收的参数是一个函数名 # 定义了一个内部函数 def bar():
print("这是新功能。。。") # 新功能 name() # 函数名加()就相当于执行--> 我传进来原函数的函数名,这里就相当于执行了原函数
return bar# 定义一个被装饰的函数def f1(): print("hello world.") # 用foo函数装饰f1函数f1 =
foo(f1)# 不改变f1的调用方式f1() # --> 此时函数已经扩展了新功能
五 装饰器形态2
# 还是定义一个外层函数def foo(name): # 接收的参数是一个函数名 # 定义了一个内部函数 def bar():
print("这是新功能。。。") # 新功能 name() # 函数名加()就相当于执行--> 传进来原函数的函数名,这里就相当于执行了原函数
return bar# 定义一个被装饰的函数# 用foo函数装饰f1函数@foo # 使用f1 =
foo(f1)语法装饰的话稍显啰嗦,Python就提供了@语法,让装饰过程更简便def f1(): print("hello world.") #
不改变f1的调用方式f1() # --> 此时函数已经扩展了新功能。
参考技术A


所谓装饰器就是把函数包装一下,为函数添加一些附加功能,装饰器就是一个函数,参数为被包装的函数,返回包装后的函数:你可以试下:

def d(fp):
    def _d(*arg, **karg):
        print "do sth before fp.."
        r= fp(*arg, **karg)
        print "do sth after fp.."
        return r
    return _d
@d
def f():
    print "call f"
#上面使用@d来表示装饰器和下面是一个意思
#f = d(f)
f()#调用f




参考技术B Python装饰器是Python中的特有变动,可以使修改函数变得更加容易。如果是自学的话,可以看黑马程序员的视频库,很多入门的课程,非常适合小白。官网对话框还可以直接领取到课程大纲。本回答被提问者采纳

Python一文弄懂python装饰器(附源码例子)

目录

前言

一、什么是装饰器

二、为什么要用装饰器

三、简单的装饰器

四、装饰器的语法糖@

五、装饰器传参

六、带参数的装饰器

七、类装饰器

八、带参数的类装饰器

九、装饰器的顺序

总结

写在后面


前言

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

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

本文的环境如下:

win10,python3.7

一、什么是装饰器

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

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

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

  1. 对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。
  2. 对修改封闭,意味着类一旦设计完成,就可以独立其工作,而不要对类尽任何修改。

二、为什么要用装饰器

使用装饰器之前,我们要知道,其实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装饰器的主要内容,如果未能解决你的问题,请参考以下文章

python基础篇:什么是装饰器?装饰器有什么用?

Python装饰器是什么?使用Python装饰器实现计算程序(函数)运行时间的功能

python装饰器 什么是python装饰器

Python 装饰器和装饰器模式有啥区别?

Python一文弄懂python装饰器(附源码例子)

Python一文弄懂python装饰器(附源码例子)