zoukankan      html  css  js  c++  java
  • python之装饰器详解

    这几天翻看python语法,看到装饰器这里着实卡了一阵,最初认为也就是个函数指针的用法,但仔细研究后发现,不止这么简单。

    首先很多资料将装饰器定义为AOP的范畴,也就是Aspect Oriented Programming面向切面编程的概念,不懂AOP不要紧,只要有函数指针的概念,又有嵌套函数的基础知识,看懂此文一点压力都没有。

    先说说为什么要有装饰器这么个东西存在吧,这是一种设计模式,较为经典的有插入日志、性能测试、事务处理等等。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。

    关于嵌套函数,C/C++程序员可能会觉得一头雾水,因为谭老爷子的书明确指出函数不可以嵌套定义,在C++ primer也仅仅有嵌套类的讲解,并不存在嵌套函数一说,但是在一些高级语言中,比如Object C,swift都存在嵌套函数主题,感兴趣的同学可以去翻一下,尤其是当下热门swift,嵌套函数和闭包密不可分,可以方便的实现很多逻辑,比如返回一个函数给调用者。

    书归正传,所谓装饰器,顾名思义,就是用来装饰的神器,你有一个函数A(),觉得它不够漂亮,但是又不想重写它,咋办呢?装饰器(Decorator)这时候就派上用场了。

    为了方便大家的理解,我先写一个简单的嵌套函数:

    def funcA():
        print("i am funcA()!")
        def funcB():
            print("i am funcB()")
        return funcB
    callSth=funcA()//调用funcA得到返回的funcB
    callSth()//相当于调用funcB
     

    值得说明一点,在python里只要你不怕蛋疼,你可以任意嵌套,哪怕你嵌套个百十层直到栈溢出都没问题。

    如下:

    def ppp():
        print("return vlaue")
    def first():
        a=10// a被嵌套内的函数捕获!
        print("the first layer")
        def second():
            print("the second layer %d" % a)
            def third():
                print("the third layer %d " % a)
                def forth():
                    print("the forth layer")
                    return ppp()
                return forth
            return third
        return second 
                     
    aaa=first()()()//得到forth()
    aaa()
    

     关于嵌套函数是这样执行的,先执行最外层的first(),返回second然后发现后面跟着(),那继续返回third,以此类推,聪明如你,肯定明白了。嵌套函数就这样,简单到爆。如果到这里诸君理解了,装饰器基本上也就理解了。

    下面正式开始装饰器的概念(开启摘抄模式):

    装饰器的定义很是抽象,我们来看一个小例子。

    def foo():
        print 'in foo()'
    foo()

    这是一个很无聊的函数没错。但是突然有一个更无聊的人,我们称呼他为B君,说我想看看执行这个函数用了多长时间,好吧,那么我们可以这样做:

    import time
    def foo():
        start = time.clock()
        print 'in foo()'
        end = time.clock()
        print 'used:', end - start
     
    foo()
    

    很好,功能看起来无懈可击。可是蛋疼的B君此刻突然不想看这个函数了,他对另一个叫foo2的函数产生了更浓厚的兴趣。

    怎么办呢?如果把以上新增加的代码复制到foo2里,这就犯了大忌了~复制什么的难道不是最讨厌了么!而且,如果B君继续看了其他的函数呢?

    以不变应万变:还记得吗,函数在Python中是一等公民,那么我们可以考虑重新定义一个函数timeit,将foo的引用传递给他,然后在timeit中调用foo并进行计时,这样,我们就达到了不改动foo定义的目的,而且,不论B君看了多少个函数,我们都不用去修改函数定义了!

    import time
     
    def foo():
        print 'in foo()'
     
    def timeit(func):
        start = time.clock()
        func()
        end =time.clock()
        print 'used:', end - start
     
    timeit(foo)
    

    看起来逻辑上并没有问题,一切都很美好并且运作正常!……等等,我们似乎修改了调用部分的代码。原本我们是这样调用的:foo(),修改以后变成了:timeit(foo)。这样的话,如果foo在N处都被调用了,你就不得不去修改这N处的代码。或者更极端的,考虑其中某处调用的代码无法修改这个情况,比如:这个函数是你交给别人使用的。

    最大限度地少改动!

    既然如此,我们就来想想办法不修改调用的代码;如果不修改调用代码,也就意味着调用foo()需要产生调用timeit(foo)的效果。我们可以想到将timeit赋值给foo,但是timeit似乎带有一个参数……想办法把参数统一吧!如果timeit(foo)不是直接产生调用效果,而是返回一个与foo参数列表一致的函数的话……就很好办了,将timeit(foo)的返回值赋值给foo,然后,调用foo()的代码完全不用修改!

    一个真正意义上的装饰器诞生了:

    #-*- coding: UTF-8 -*-
    import time
     
    def foo():
        print 'in foo()'
     
    # 定义一个计时器,传入一个,并返回另一个附加了计时功能的方法
    def timeit(func):
         
        # 定义一个内嵌的包装函数,给传入的函数加上计时功能的包装
        def wrapper():
            start = time.clock()
            func()
            end =time.clock()
            print 'used:', end - start
         
        # 将包装后的函数返回
        return wrapper
     
    foo = timeit(foo)
    foo()
    

    样,一个简易的计时器就做好了!我们只需要在定义foo以后调用foo之前,加上foo = timeit(foo),就可以达到计时的目的,这也就是装饰器的概念,看起来像是foo被timeit装饰了。在在这个例子中,函数进入和退出时需要计时,这被称为一个横切面(Aspect),这种编程方式被称为面向切面的编程(Aspect-Oriented Programming)。与传统编程习惯的从上往下执行方式相比较而言,像是在函数执行的流程中横向地插入了一段逻辑。在特定的业务领域里,能减少大量重复代码。面向切面编程还有相当多的术语,这里就不多做介绍,感兴趣的话可以去找找相关的资料。

    语法糖

    上面这段代码看起来似乎已经不能再精简了,Python于是提供了一个语法糖来降低字符输入量。

    import time
     
    def timeit(func):
        def wrapper():
            start = time.clock()
            func()
            end =time.clock()
            print 'used:', end - start
        return wrapper
     
    @timeit// syntax sugar 是也,在定义上加上这一行与另外写foo = timeit(foo)完全等价,千万不要以为@有另外的魔力。除了字符输入少了一些,还有一个额外的好处:这样看上去更有装饰器的感觉。
    def foo():
        print 'in foo()'
     
    foo()
    

     给出原文链接:http://www.cnblogs.com/huxi/archive/2011/03/01/1967600.html

    相关资料:http://blog.csdn.net/thy38/article/details/4471421

    http://www.cnblogs.com/rhcad/archive/2011/12/21/2295507.html

  • 相关阅读:
    玩转Android之手摸手教你DIY一个抢红包神器!
    NetWork——关于TCP协议的三次握手和四次挥手
    请保持心情快乐,请保持情绪稳定
    第八节:Task的各类Task<TResult>返回值以及通用线程的异常处理方案。
    第七节:利用CancellationTokenSource实现任务取消和利用CancellationToken类检测取消异常。
    第六节:深入研究Task实例方法ContinueWith的参数TaskContinuationOptions
    第五节:Task构造函数之TaskCreationOptions枚举处理父子线程之间的关系。
    第四节:Task的启动的四种方式以及Task、TaskFactory的线程等待和线程延续的解决方案
    第三节:ThreadPool的线程开启、线程等待、线程池的设置、定时功能
    第二节:深入剖析Thread的五大方法、数据槽、内存栅栏。
  • 原文地址:https://www.cnblogs.com/wangjunyan/p/5046223.html
Copyright © 2011-2022 走看看