趣玩Python之装饰器详解
趣玩Python之装饰器详解
水果糖 发表于1年前
趣玩Python之装饰器详解
  • 发表于 1年前
  • 阅读 115
  • 收藏 3
  • 点赞 1
  • 评论 0

腾讯云 技术升级10大核心产品年终让利>>>   

摘要: 趣玩Python系列(禁止转载)

    装饰器,顾名思义为某种事物添加一个装饰;其实就是对函数进行“闭包”处理,从而隐藏复杂的逻辑实现、日志记录、性能测试、保持事务的一种方法。在面向切面需求中可以经常看到。

    用通俗语来说:“装饰器其实就是相当于一个包裹;同样是运行你的函数,只不过在运行你的函数之前与之后,装饰器(函数)可以进行一些上述相关操作而已,而这些操作能方便你最小化修改代码。”

 

下面我们看一个简单的例子:

装饰器 ① (最简单的装饰器)

#-*- coding:utf-8 -*-

def main(func):
    def work():
        print "装饰器开始运行"
        func()
        print "装饰器结束运行"
    return work


@main
def abc():
    print "     Candy's Comming!"

#上面等效 abc = main(abc)


abc()

 

输出结果:

C:\Python\python.exe C:/main.py

装饰器开始运行
     Candy's Comming!
装饰器结束运行

Process finished with exit code 0

 

    请将上述代码复制到您的IDE内并运行! 11 行的装饰器代码 等效于 abc = main(abc) ;这样我们就构造了abc函数运行的顺序,并用print函数将其运行顺序记录了下来。

    如果对此有疑惑,你在print abc.__name__ 的时候就能发现,abc.__name__变成了work。语法糖就是如此的奇妙。

    如果你理解了第一个例子,我们就来看看接下来如何装饰有参数的函数。

 

装饰器 ② (带参数的装饰器)

#-*- coding:utf-8 -*-

def main(func):
    def work(*args):
        print "装饰器开始运行"
        func(*args)
        print "装饰器结束运行"
    return work


@main
def abc(Say = ""):
    print "     %s's Comming!" % Say


abc("Candy")

 

运行结果如下:

C:\Python\python.exe C:/main.py

装饰器开始运行
     Candy's Comming!
装饰器结束运行

Process finished with exit code 0

 

    由于装饰器装饰过后,实际abc是work的引用。所以我们调用abc(string)这样的函数实际上是调用work函数;而work函数运行后,拿到Main函数的参数func作为函数、自己的参数*args作为func的参数执行。

    所以,结果输出 : "Candy' Comming!"

 

装饰器 ③:(类方法实现的装饰器)

# -*- coding:utf-8 -*-

class Main(object):

    @classmethod
    def Method(self,Say = "Hello"):
        def main(func):
            def work(*args,**kwargs):
                print Say
                print "装饰器开始运行..."
                result = func(*args,**kwargs)
                print "装饰器结束运行..."
                return result
            return work
        return main

@Main.Method("hello world!")
def abc(a,b):
    print abc.__name__
    return a + b

print "打印运行结果:%s" % abc(1,2)

 

运行结果如下:

C:\Python\python.exe C:/main.py

hello world!
装饰器开始运行...
work
装饰器结束运行...
打印运行结果:3

Process finished with exit code 0

 

    注意:由于类方法不能直接被类本身调用,所以我们需要在方法之前加上classmethod装饰,这样就可以被类本身所调用了。而这个方法由于是用C来实现的,如果有需要的同学可以参考Python源码实现。

    既然可以直接用类调用Method方法了,那么装饰器就可以直接写成 classname.Method(*args,**kwargs);我们给装饰器传入一个参数(注:给被装饰的函数传参与给装饰器传参是不同的)Say = "Hello world" ;然后Method方法返回了一个main(abc)这样的引用给abc,这时abc就相当于main函数了。这时我们再abc并给它传入2个参数(1,2),这就相当于运行了main函数内部的代码并且把参数1与2传入给*args和**kwargs。然后就开始正常运行内部带代码,打印、返回结果.....

    这里还有一个地方需要注意的是,在23行 print 关键字打印结果时,如果你用的不是'%'而是','的话,这将会导致运行结果与上述不一致。至于为什么不一致,就当Candy留给大家做观后思考吧! ^  _  ^

 

装饰器 ④:(为类方法提供参数)

#-*- coding:utf-8 -*-

class Flask:
    @classmethod
    def route(self,View_Route = '/',Method = "GET"):
        def wrap(cls):
            def work(*args,**kwargs):
                print "请求路由为:",View_Route
                print "请求方法为:",Method
                print "装饰器开始运行..."
                obj = cls(*args, **kwargs)
                print "装饰器结束运行..."
                return obj
            return work
        return wrap


@Flask.route("/",Method="POST")
class main(object):
    def __init__(self):
        print "Main被初始化"
        print "Main初始化结束"

    def work(self):
        print "main.work()被调用..."

m = main()
m.work()

 

运行结果如下:

C:\Python\python.exe C:/main.py

请求路由为: /
请求方法为: POST
装饰器开始运行...
Main被初始化
Main初始化结束
装饰器结束运行...
main.work()被调用...

Process finished with exit code 0

 

    上述代码是模仿Flask路由方法写的实例;如果你看过前三个装饰器的实例应该就会知道,类在初始化之前将调用我们的Flask.route装饰器并带上2个参数(请求路由 "/" 与 请求方法" POST");而route方法在接受到2个参数后返回一个wrap函数对象,也就是说在m = main()这一行,main()不是一个class而是一个wrap函数;这一行相当于运行wrap(),此时将上面被装饰的class main实例化后返回一个对象赋值给m,再由m来调用class main的方法work()进行打印输出。

 

    装饰器在装饰类与装饰函数时大致是一致的,唯一的区别在于类需要初始化而函数不需要。也正应为如此,导致大家在理解类装饰器的时候对某些运行步骤有些难以理解也实属正常。这里也只是浅显的点了一下装饰类即可,因为在大型与超大型项目程序设计中,大量的类装饰器是很难见到的。

 

  ————————————————————华丽的分割线————————————————————

 

    以上就是装饰器实现闭包的实际运行过程,但是这不是一个"无污染"的闭包;因为在被装饰的函数内打印自己的 __name__ 内部变量时显示的不是被装饰函数的名字,从而导致原函数的相关信息在闭包构造期间就被取代了。

    这时,我们可以使用一个functiontools 模块内置wrap这个装饰器将func函数再装饰一次。从而将在work函数内运行的func相关属性修改回来。源代码中是使用偏函数partial来构造的,而partial又是内置方法使用C语言来实现的。所以在此我们只需要知道运行流程是怎样的即可,具体实例可以参考官方。

    有兴趣的同学也可以去看看functiontools模块,有不懂的同学可以留言与我进行交流。

    由于在匆促之间完成,文字、逻辑之间难免有错误!欢迎大家指正! ^ _ ^

 

共有 人打赏支持
粉丝 12
博文 127
码字总数 56429
×
水果糖
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: