Python装饰器详解

 python中的装饰器是一个用得非常多的东西,我们可以把一些特定的方法、通用的方法写成一个个装饰器,这就为调用这些方法提供一个非常大的便利,如此提高我们代码的可读性以及简洁性,以及可扩展性。

在学习python装饰器之前我们先看看这样一个例子:

一、作用域

# coding:utf-8 msg = 'hello test1'  def add():    msg = 'this is add'    print msg     #当执行add()时将打印'this is add'def add2():    print msg     #当执行add2()时将打印'hello test1'

登录后复制

    上面的例子简单地对python的作用域做了一个说明,例子中申明了全局变量msg,add函数中也申明了一个局部变量msg,当执行add()的 “print msg” 时会先找add里面是否有局部变量msg,如果没找到就去上一级作用域找是否存在该变量,局部变量在函数运行时生成,当函数结束运行时局部变量也随之销毁,接下来我们对上面的例子加深:

二、闭包

# coding:utf-8 def add():    msg = 'hello add'    def inner():        print msg               #运行add()这里会打印'hello add'    return inner

登录后复制

    

>>> obj = add()>>> obj            #可见obj是一个指向inner函数的对象...>>> obj()hello add           #运行obj()打印msg

登录后复制

   

立即学习“Python免费学习笔记(深入)”;

  看了上面的例子有没有一点疑惑,obj是指向inner函数的对象,当运行obj时就等同于运行inner,但是add函数并没有运行,也就是说msg没有被申明同时inner也没有申明变量msg,它又是如何找到msg这个变量的值呢?

  这就是python里的”闭包”,Python支持一个叫做函数闭包的特性,用人话来讲就是,嵌套定义在非全局作用域里面的函数能够记住它在被定义的时候它所处的封闭命名空间。这能够通过查看函数的obj.func_closure属性得出结论,这个属性里面包含封闭作用域里面的值(只会包含被捕捉到的值,如果在add里面还定义了其他的值,封闭作用域里面是不会的)闭包就是python装饰器的核心原理,接下来我们写一个简单的装饰器例子:

三、简单的装饰器

# coding:utf-8 def check_args_num(func):    # 该装饰器用于检查传入的参数数量,检查是否是两个参数    def inner(*args, **kwargs):        args_list = list(args)        if len(args_list)  2:            # 如果参数数量大于2则打印错误            print 'The args number is too many!'        func(*args_list, **kwargs)    return inner  @check_args_numdef add(x, y):    return x + y

登录后复制

   

立即学习“Python免费学习笔记(深入)”;

执行结果:

>>>print add(1,2)3...>>>print add(100)100...>>>print add(1,2,3)Traceback (most recent call last):  File "D:PyCharm 5.0.4helperspydevpydevd_exec.py", line 3, in Exec    exec exp in global_vars, local_vars  File "", line 1, in   File "E:/code/my_project/decorator/test1.py", line 14, in inner    raise Exception('The args number is too many!')Exception: The args number is too many!...>>>add#可以看到add函数现在指向的是inner

登录后复制

   

立即学习“Python免费学习笔记(深入)”;

四、多个装饰器

# coding:utf-8 def check_args_int(func):    # 该装饰器用于检查传入的参数是否是int型    def ensure_int(*args, **kwargs):        from array import array        try:            array('H', args)        except Exception, e:            raise Exception(e)        return func(*args, **kwargs)     return ensure_int  def check_args_num(func):    # 该装饰器用于检查传入的参数数量,检查是否是两个参数    def inner(*args, **kwargs):        args_list = list(args)        if len(args_list)  2:            # 如果参数数量大于2则打印错误            raise Exception('The args number is too many!')        return func(*args_list, **kwargs)     return inner  @check_args_num@check_args_intdef add(x, y):    return x + y

登录后复制

   

立即学习“Python免费学习笔记(深入)”;

这里多加了一个参数内容检查的装饰器,当多个装饰器时,会自上而下执行,先执行check_args_num再执行check_args_int,执行结果:

>>> print add(1,'fsaf')Traceback (most recent call last):  File "D:PyCharm 5.0.4helperspydevpydevd_exec.py", line 3, in Exec    exec exp in global_vars, local_vars  File "", line 1, in   File "E:/code/my_project/decorator/test1.py", line 28, in inner    return func(*args_list, **kwargs)  File "E:/code/my_project/decorator/test1.py", line 10, in ensure_int    raise Exception(e)Exception: an integer is required...>>> add

登录后复制

#这里可以看到add还是指向inner,我们可以这样理解当多个装饰器存在时,当调用add时,其调用入口始终是第一个装饰器,第一个装饰器执行完,再执行下一个,同时也会将参数依次传递下去

   

立即学习“Python免费学习笔记(深入)”;

五、带参数的装饰器

我们知道定义装饰器的时候传入装饰器的第一个参数是被装饰的函数(eg.例子中的add),有些时候我们也需要给装饰器传递额外的参数,下面例子将给装饰器传递额外参数,如下:

# coding:utf-8 def check_args_int(func):    # 该装饰器用于检查传入的参数是否是int型    def ensure_int(*args, **kwargs):        from array import array        try:            array('H', args)        except Exception, e:            raise Exception(e)        return func(*args, **kwargs)     return ensure_int  def check_args_num(flag):    '''    :param func: 被装饰函数    :param flag: 决定是否检查参数数量    '''     # 该装饰器用于检查传入的参数数量,检查是否是两个参数    def get_func(func):         def inner(*args, **kwargs):            if flag == 'false':                print 'Skip check !'                return func(*args, **kwargs)            args_list = list(args)            if len(args_list)  2:                # 如果参数数量大于2则打印错误                raise Exception('The args number is too many!')            return func(*args_list, **kwargs)         return inner     return get_func  @check_args_num('false')@check_args_intdef add(x, y):    return x + y

登录后复制

   

立即学习“Python免费学习笔记(深入)”;

这个例子只有check_args_num和之前的有所不同,不同在于装饰器check_args_num多了一个参数flag,当flag==’false’时,跳过参数数量检查,下面是输出结果

>>>print add(1, 2)Skip check !3

登录后复制

   

立即学习“Python免费学习笔记(深入)”;

六、decorator不带参数的装饰器

  decorator模块是python用来专门封装装饰器的模块,使用decorator构造装饰器更加简便,同时被装饰的函数签名也保留不变
  之前的讲了通过闭包实现python装饰器构造,这里利用decorator模块实现python装饰器与其原理都是一样的

from decorator import decorator  @decoratordef check_num(func, *args, **kwargs):    if len(args) != 2:        raise Exception('Your args number is not two')    else:        return func(*args, **kwargs)  @check_numdef add(x, y):    return x + y

登录后复制

    

>>> add>>> print add(1,2)3...>>> add(1,2,3)Traceback (most recent call last):  File "D:PyCharm 5.0.4helperspydevpydevd_exec.py", line 3, in Exec    exec exp in global_vars, local_vars  File "", line 1, in TypeError: add() takes exactly 2 arguments (3 given) #可以看到这里当我们传三个参数给add()函数时,他直接从add()函数抛出类型错误异常,#并没有进入check_num装饰器进行参数校验,可见被装饰的函数add()的签名还是他本身#如果直接构造装饰器,那么这里将会从check_num里面抛出异常,如下: def check_num(func):    def inner(*args,**kwargs):        if len(args) != 2:            raise Exception('Your args number is not two')        else:            return func(*args,**kwargs)    return inner @check_numdef add(x, y):    return x + y  >>> add>>>add(1,2,3)Traceback (most recent call last):  File "D:PyCharm 5.0.4helperspydevpydevd.py", line 2411, in     globals = debugger.run(setup['file'], None, None, is_module)  File "D:PyCharm 5.0.4helperspydevpydevd.py", line 1802, in run    launch(file, globals, locals)  # execute the script  File "E:/code/my_project/decorator/test3.py", line 14, in     print add(1,2,3)  File "E:/code/my_project/decorator/test3.py", line 4, in inner    raise Exception('Your args number is not two')Exception: Your args number is not two

登录后复制

   

立即学习“Python免费学习笔记(深入)”;

从上面的例子可以看出,通过闭包的方式构造装饰器时,其执行函数入口是装饰器中的嵌套函数,这样就可能出现上面出现的问题,当add(1,2,3)执行时会先执行inner函数(如果inner里面没有参数校验则这里是不会抛出异常的,只有当执行到return func(*args,**kwargs)时才真正调用add(x,y)函数,这时候才会抛出异常)这样就会造成程序执行多余的代码,浪费内存,cpu。

七、decorator带参数的装饰器

如果想让装饰器带参数呢?

from decorator import decorator  def check(flag):    @decorator    def check_num(func, *args, **kwargs):        if flag == 'false':            print 'skip check !'            return func(*args,**kwargs)        if len(args) != 2:            raise Exception('Your args number is not two')        else:            return func(*args, **kwargs)    return check_num  @check('false')def add(x, y):    return x + y >>>add>>>add(1,2)skip check !3

登录后复制

   

立即学习“Python免费学习笔记(深入)”;

  decorator 模块就讲到这里,这个模块比较简单,还有一些功能没讲到的查看源码便一目了然,其原理都是利用python闭包实现的

 八、functools.wraps(func)装饰器

   functools.wraps和decorator模块的功能是一样的,都是为了解决被装饰函数的签名问题,这里只对该种装饰器构造方法列举一个带参数例子:

import functools def check(flag):    def wraps(func):        @functools.wraps(func)        def check_num(*args, **kwargs):            if flag == 'false':                print 'skip check !'                return func(*args,**kwargs)            if len(args) != 2:                raise Exception('Your args number is not two')            else:                return func(*args, **kwargs)        return check_num    return wraps @check('false')def add(x, y):    return x + y

登录后复制

   

立即学习“Python免费学习笔记(深入)”;

  对比上面通过decorator模块装饰函数的例子,我们可以发现,用decorator装饰函数的代码更加简洁易懂,但是他们二者的执行效率谁更高呢?下面我们通过Timer来测试下:

from timeit import Timer print Timer('add(1,2)',setup='from __main__ import add').timeit(100000) #将该段代码 加在 之前的例子中#这里打印的是运行100000次的时间

登录后复制

   

立即学习“Python免费学习笔记(深入)”;

  functools.wraps装饰执行结果:

2.37299322602

登录后复制

   

立即学习“Python免费学习笔记(深入)”;

  decorator模块装饰执行结果:

3.42141566059

登录后复制

   

立即学习“Python免费学习笔记(深入)”;

  执行效率wraps略高,但是这里是执行了10万次他们之间的差距约为1秒,所以我个人还是比较青睐于用decorator模块装饰函数,毕竟看起来易懂,写法也较为简单!本文就将装饰器介绍到这里了,当然也没有说尽装饰器的妙用,比如:装饰类…其原理是用类来当做装饰器,类里面需要用到__call__方法,至于装饰类的用法感兴趣的朋友自行百度咯!

 

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至253000106@qq.com举报,一经查实,本站将立刻删除。

发布者:PHP中文网,转转请注明出处:https://www.chuangxiangniao.com/p/2282383.html

(0)
上一篇 2025年2月27日 19:24:06
下一篇 2025年2月19日 08:46:56

AD推荐 黄金广告位招租... 更多推荐

相关推荐

  • 如何系统地自学一门Python 语言

    零基础情况下,学一门语言充实下自己,python,简洁、优美、容易使用,是一个很好的选择。那么如何系统地自学python呢? 有的人非常想学好Python,一方面被琐事纠缠,一直没能动手,另一方面,担心学习成本太高,心里默默敲着退堂鼓?  …

    2025年2月27日
    200
  • python中round(x,[n])的使用

    round(x,[n]) 四舍五入 n保留小数部分个数 1、当不填写n时,默认为0,即不保留小数部分  a、当小数部分只为0.5时 整数部分如果为奇数则进1,如果为偶数则舍去小数部分  print(round(1.5))#2 小数部分为0.…

    编程技术 2025年2月27日
    200
  • python爬虫beta版之抓取知乎单页面

    鉴于之前用python写爬虫,帮运营人员抓取过京东的商品品牌以及分类,这次也是用python来搞简单的抓取单页面版,后期再补充哈。 #-*- coding: UTF-8 -*- import requestsimport sysfrom b…

    2025年2月27日
    200
  • python 数据类型 —字符串

    字符串去除空白 ,strip() , 包括空格,tab键, 换行符 >>> name = ”  Frank  “>>> name.strip()’Frank’ 登录后复制 字符串的分割, split(&#…

    编程技术 2025年2月27日
    200
  • python快速生成注释文档的方法

    python快速生成注释文档的方法  今天将告诉大家一个简单平时只要注意的小细节,就可以轻松生成注释文档,也可以检查我们写的类方法引用名称是否重复有问题等。一看别人专业的大牛们写的文档多牛多羡慕,不用担心我们可以让python为我们生成基本…

    2025年2月27日 编程技术
    200
  • python操作SQL

    pymsql是python中操作mysql的模块,其使用方法和mysqldb几乎相同 一、下载安装 pip3 install pymysql 二、操作使用 1、执行SQL 立即学习“Python免费学习笔记(深入)”; #!/usr/bin…

    2025年2月27日
    200
  • 通过reidis管理定时任务

    主要应用场景为:有变动需求的一次性定时任务。 通过redis过期事件的监听,执行相应命令。(注意:因为监听只能得到key, 所以需要另外存储具体执行内容体) 另外记得修改redis配置:notify-keyspace-events ex  …

    编程技术 2025年2月27日
    200
  • Python——将字符串转换为日期

    python中标准模块datetime能够将字符串转换为日期  from datetime import datetime   text = ‘2012-09-20’   y = datetime.strptime(…

    编程技术 2025年2月27日
    200
  • python——计算上周5的日期

    第一种方法:   from datetime import datetime, timedelta  weekdays = [‘Monday’,’Tuesday’,’Wednesday’,’Thursday’,              ‘…

    编程技术 2025年2月27日
    200
  • python java 调用

        jython    jython    2.1  登录后复制 package com.curiousby.python.demo;import org.python.core.PyFunction;import org.python…

    编程技术 2025年2月27日
    200

发表回复

登录后才能评论