理解什么是面向对象

首先我们需要知道在python当中,一切都是对象

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

cal(1,2)
caculator = cal
caculator(1, 2)

例如上面这段代码,我们声明了一个非常简单的函数cal,并且创建了一个cal的函数对象caculator=cal这一部相当于是把caculator这个变量指向了我的cal函数对象,被称为实例化,接下来我使用caculator(1, 2)也可以得到cal(1,2)一样的效果。

装饰器的起因

设想一个场景,最近妇联4要上映了,托尼斯塔克有很多套装甲,没加装饰器之前,托尼都需要一件一件到指定的存储装甲的房间手动穿上去,当加上装饰器后,托尼只需要站到机器人的地方,选择合适的装甲,就可以由机器人自动为其穿上。这里的穿装甲的机器人相当于是装饰器。

def update_ironman(func):
    def wear(*arg, **kw):
        print("穿上了装甲")
        return func(*arg, **kw)
    return wear

def ironman():
    print("我是托尼斯塔克")

ironman = update_ironman(ironman)
ironman()

上面这段例子,传入update_ironman这个函数的参数是一个函数,所以ironman = update_ironman(ironman)这一部的结果相当于是ironman=wear,因为update_ironman最后返回的是wear,当我们后面执行了ironman(),相当于是执行了wear(),由wear函数内部执行了上一步传入的参数(ironman这个函数)。当搞清楚这个流程后,我们就可以继续深入。

这样写是不是很麻烦,所以有了@的语法糖,只要在需要装饰的函数前面加上@就可以起到装饰器的作用

def update_ironman(func):
    def wear(*arg, **kw):
        print("穿上了装甲")
        return func(*arg, **kw)
    return wear

@update_ironman # @update_ironman相当于ironman = update_ironman(ironman) 相当于 ironman = wear
def ironman():
    print("我是托尼斯塔克")

ironman()

结果

穿上了装甲
我是托尼斯塔克

带参数的装饰器

刚刚托尼只是实现了穿上装甲,现在托尼需要告诉机器人我今天要穿哪套装甲去战斗,这里牵涉到一个概念就是要告诉机器人哪套装甲这个参数问题。所以我们对我们的代码进行变动

def choice(whichone):
    def update_ironman(func):
        def wear(*arg, **kw):
            print("穿上了%s装甲"%whichone)
            return func(*arg, **kw)
        return wear
    return update_ironman

@choice(whichone="MARK2")
def ironman():
    print("我是托尼斯塔克")

ironman()

这里我们对刚刚的装饰器,外面又套了一层函数,这样我们在下面使用我们的语法糖的时候,就可以在装饰器中传入参数,也就是告诉我们的机器人需要哪套装甲
最后的执行效果

穿上了MARK2装甲
我是托尼斯塔克

多个装饰器

def update_weapon(func):
    print("开始装备武器")
    def wear_weapon(*arg, **kw):
        print("武器装备完成")
        return func(*arg, **kw)
    return wear_weapon

def update_ironman(func):
    print("开始穿上装甲")
    def wear_armor(*arg, **kw):
        print("装甲固定完成")
        return func(*arg, **kw)
    return wear_armor


@update_weapon
@update_ironman
def ironman():
    print("我是托尼斯塔克")

ironman()

上面的代码等效于update_weapon(update_ironman(ironman))
现在我把程序修改成这样,是不是按照之前的理解结果答案应该是(函数由内而外进行执行)

穿上装甲->装上武器

然而实际的结果是

开始穿上装甲
开始装备武器
武器装备完成
装甲固定完成
我是托尼斯塔克

所以这里我们需要明白一个概念:函数和函数调用的区别

上面的ironman称之为函数,ironman()称之为函数调用

当我们对ironman进行调用的时候,python解释器发现上面有个update_ironman的装饰,所以调用update_ironman这个函数,于是执行了开始穿上装甲这句话,此时ironman = wear_armor ,传入update_armor这个函数的参数是ironman这个函数的名字,但是python解释器发现事情并没有这么简单,上面还有个@update_weapon这个装饰器,所以继续对ironman这个函数进行装饰,调用了update_weapon这个函数,于是执行了开始装备武器将ironman作为参数func传入update_weapon中,因为此时ironman=wear_armor,所以相当于是传入了wear_armor这个函数,返回了wear_weapon,最后ironman = wear_weapon,但是到这里ironman函数并没有执行

当我们执行了ironman()这个函数,因为刚刚ironman=wear_weapon,所以会先去执行wear_weapon这个函数,所以打印了武器装备完成,因为刚刚传入update_weapon这个函数参数func=wear_armor,所以接着调用了wear_armor,所以打印了装甲固定完成,接着因为update_ironman这个函数传入的func=ironman,所以最后才执行了ironman这个函数,打印了我是托尼斯塔克

整个流程总结如下:
1. 先执行了update_ironman,打印了开始穿上装甲,此时ironman = wear_armor,传入的参数func=ironman
2. 再执行了update_weapon,打印了开始装备武器, 此时ironman = wear_weapon,传入的参数func=wear_armor
3. 执行ironman()后,执行wear_weapon,执行func(),相当于运行wear_armor()
4. 运行wear_armor()后,执行func(),相当于运行ironman()

所以由此可见上面托尼的穿装甲的逻辑错了应该先
1. @update_ironman
2. @update_weapon

总结

装饰器可以用扩展功能,或者实现权限认证。

写的方式,从上到下,按照业务的逻辑来依次加上装饰器

执行的顺序,装饰器由内而外执行,调用时函数由外至内执行~

后续学到了类装饰器,再继续补充

版权声明:本文为原创文章,版权归 heroyf 所有。
本文链接: https://heroyf.club/2019/04/python_decator/


“苹果是给那些为了爱选择死亡的人的奖励”