如何理解Python装饰器?

尽量有中文的资料,浅显一些,好理解的,谢谢
关注者
2,237
被浏览
949,026
登录后你可以
不限量看优质回答私信答主深度交流精彩内容一键收藏

之前写了一个1.0版,现在把更加完整的2.0版奉上。

1.0版已经被我杀掉了。

--------------------------------------2.0版-------------------------------------------------------------------

Attention:为了解释得通俗易懂,所以很多概念没有描述的很准确,很多只是“意思意思”,所以大家重在意会哈。如果各位知友有更好的解释和讲解的方法,还请在评论区不吝赐教。

学习python有一段时间了,对于装饰器的理解又多了一些,现在我重新再写一次对于装饰器的理解。

在讲之前我需要先铺垫一下基础知识,如果你已经掌握了就请跳过。

--------------------------------------基础知识分割线-----------------------------------------------------

1、万物皆对象

在python中,不管什么东西都是对象。对象是什么东西呢?

对象就是你可以用来随意使用的模型。当你需要的时候就拿一个,不需要就让它放在那,垃圾回收机制会自动将你抛弃掉的对象回收。

可能对这个理解有一点云里雾里的感觉,甚至还觉得对这个概念很陌生。其实如果你都学到装饰器这里了,你已经使用过不少对象啦。

比如,我写了一个函数:

def cal(x, y):
    result = x + y
    return result

这时,你可以说,你创造了一个叫做cal的函数对象。

然后,你这样使用了它:

cal(1,2)

或者,你这样使用了它:

calculate = cal
calculate(1,2)

在第一种方式下,你直接使用了cal这个函数对象;

在第二种方式下,你把一个名为calculate的变量指向了cal这个函数对象。如果各位对类的使用很熟悉的话,可以把这个过程看作“实例化”。

也就是说,对象,就像是一个模子,当你需要的时候,就用它倒一个模型出来,每一个模型可以有自己不同的名字。在上面的例子中,calculate是一个模型,而你写的cal函数就是一个模子。

2、请理解函数带括号和不带括号时分别代表什么意思。

在上一个例子中,如果你只是写一个cal(也就是没有括号),那么此时的cal仅仅是代表一个函数对象;当你这样写cal(1, 2)时,就是在告诉编译器“执行cal这个函数”。

3、请确保能够理解带星号的参数是什么意思。

这个属于函数基础,要是你还没有听说过,那么就该回去好好复习一下了。具体讲解我就略过了。

----------------------------------------------正文分割线---------------------------------------------------

1、装饰器是什么?

装饰器,顾名思义,就是用来“装饰”的。

它长这个样:

@xxx

其中"xxx"是你的装饰器的名字。

它能装饰的东西有:函数、类

2、为什么我需要装饰器?

有一句名言说的好(其实是我自己说的):

“每一个轮子都有自己的用处”

所以,每一个装饰器也有自己的用处。

装饰器主要用来“偷懒”(轮子亦是如此)。

比如:

你写了很多个简单的函数,你想知道在运行的时候是哪些函数在执行,并且你又觉得这个没有必要写测试,只是想要很简单的在执行完毕之前给它打印上一句“Start”,那该怎么办呢?你可以这样:

def func_name(arg):
    print 'Start func_name'
    sentences

这样做没有错,but, 你想过没有,难道你真的就想给每一个函数后面都加上那么一句吗?等你都运行一遍确定没有问题了,再回来一个一个的删掉print不觉得麻烦吗?什么?你觉得写一个还是不麻烦的,那你有十个需要添加的函数呢?二十个?三十个?(请自行将次数加到超过你的忍耐阈值)……

如果你知道了装饰器,情况就开始渐渐变得好一些了,你知道可以这样写了:

def log(func):
    def wrapper(*arg, **kw):
        print 'Start %s' % func
        return func(*arg, **kw)
    return wrapper
  
@log
def func_a(arg):
    pass

@log
def func_b(arg):
    pass

@log
def func_c(arg):
    pass

其中,log函数是装饰器。

把装饰器写好了之后,只需要把需要装饰的函数前面都加上@log就可以了。在这个例子中,我们一次性就给三个函数加上了print语句。

可以看出,装饰器在这里为我们节省了代码量,并且在你的函数不需要装饰的时候直接把@log去掉就可以了,只需要用编辑器全局查找然后删除即可,快捷又方便,不需要自己手工的去寻找和删除print的语句在哪一行。

-----------------------------------------------重点分割线--------------------------------------------------

3、装饰器原理

在上一段中,或许你已经注意到了"log函数是装饰器"这句话。没错,装饰器是函数。

接下来,我将带大家探索一下,装饰器是怎么被造出来的,来直观的感受一下装饰器的原理。

先回到刚才的那个添加'Start'问题。

假设你此时还不知道装饰器。

将会以Solution的方式呈现。

S1 我有比在函数中直接添加print语句更好的解决方案!

于是你这样做了:

def a():
    pass
def b():
    pass
def c():
    pass

def main():
    print 'Start a'
    a()
    print 'Start b'
    b()
    print 'Start c'
    c()

感觉这样做好像没什么错,并且还避免了修改原来的函数,如果要手工删改print语句的话也更方便了。嗯,有点进步了,很不错。

S2 我觉得刚刚那个代码太丑了,还可以再优化一下!

于是你这样写了:

def a():
    pass
def b():
    pass
def c():
    pass

def decorator(func):
    print 'Start %s'% func
    func()

def main():
    decorator(a)
    decorator(b)
    decorator(c)

你现在写了一个函数来代替你为每一个函数写上print语句,好像又节省了不少时间。你欣喜的喝了一口coffee,对自己又一次做出了进步感到很满意。

嗯,确实是这样。

于是你选择出去上了个厕所,把刚刚憋的尿全部都排空(或许还有你敲代码时喝的coffee)。

回来之后,顿时感觉神清气爽!你定了定神,看了看自己刚才的“成果”,似乎又感到有一些不满意了。

因为你想到了会出现这样的情况:

def main():
    decorator(a)
    m = decorator(b)
    n = decorator(c) + m
    for i in decorator(d):
        i = i + n
    ......

来,就说你看到满篇的decorator你晕不晕!大声说出来!

S3 你又想了一个更好的办法。

于是你这样写了:

def a():
    pass
def b():
    pass
def c():
    pass

def decorator(func):
    print 'Start %s' % func
    return func

a = decorator(a)
b = decorator(b)
c = decorator(c)

def main():
    a()
    b()
    c()

这下总算是把名字给弄回来了,这样就不会晕了。你的嘴角又一次露出了欣慰的笑容(内心OS:哈哈哈,爷果然很6!)。于是你的手习惯性的端起在桌上的coffee,满意的抿了一口。

coffee的香味萦绕在唇齿之间,你满意的看着屏幕上的代码,突然!脑中仿佛划过一道闪电!要是a、b、c 三个函数带参数我该怎么办?!

你放下coffee,手托着下巴开始思考了起来,眉头紧锁。

像这样写肯定不行:

a = decorator(a(arg))

此时的本应该在decorator中做为一个参数对象的a加上了括号,也就是说,a在括号中被执行了!你只是想要a以函数对象的形式存在,乖乖的跑到decorator中当参数就好了。执行它并不是你的本意。

那该怎么办呢?

你扶了扶眼镜,嘴里开始念念有词“万物皆对象万物皆对象……”

你的额头上开始渐渐的渗出汗珠。

突然,你的身后的背景暗了下来,一道光反射在眼镜上!不自觉的说了句

“真相はひとつだけ”!

S4 你飞速的写下如下代码。

def a(arg):
    pass
def b(arg):
    pass
def c(arg):
    pass

def decorator(func):
    def wrapper(*arg, **kw)
        print 'Start %s' % func
        return func(*arg, **kw)
    return wrapper

a = decorator(a)
b = decorator(b)
c = decorator(c)

def main():
    a(arg)
    b(arg)
    c(arg)

decorator函数返回的是wrapper, wrapper是一个函数对象。而a = decorator(a)就相当于是把 a 指向了 wrapper, 由于wrapper可以有参数,于是变量 a 也可以有参数了!

终于!你从焦灼中解脱了出来!

不过, 有了前几次的经验,你这一次没有笑。

你又仔细想了想,能不能将a = decorator(a)这个过程给自动化呢?

于是你的手又开始在键盘上飞快的敲打,一会儿过后,你终于完成了你的“作品”。

你在python中添加了一个语法规则,取名为“@”,曰之“装饰器”。

你此时感觉有些累了, 起身打开门, 慢步走出去,深吸一口气,感觉阳光格外新鲜。

你的脸上终于露出了一个大大的笑容。

-------------------------------乱侃结束分割线------------------------------------------------------------

讲到这里,我想大家应该差不多都明白了装饰器的原理。

在评论中有知友问到,要是我的装饰器中也有参数该怎么办呢?

要是看懂了刚才添加参数的解决方案,也就不觉得难了。

再加一层就解决了。

def decorator(arg_of_decorator):
    def log(func):
        def wrapper(*arg, **kw):
            print 'Start %s' % func
            #TODO Add here sentences which use arg_of_decorator 
            return func(*arg, **kw)
       return wrapper
    return log

感谢阅读 : )

----------------------------------------------------------------------------------------------------------------

请大家着重理解思路。

答主是一名在校大学生,主修英语专业,目前大二,希望能够和各位知友多多交流。要是有同道中人关注我就更好了!

我先撤退了。

撒花!✿✿ヽ(゚▽゚)ノ✿