一、概念

  装饰器是Python语言中的高级语法。主要的功能是对一个函数、方法、或者类进行加工,作用是为已经存在的对象添加额外的功能,提升代码的可读性。装饰器是设计模式的一种,被用于有切面需求的场景,较为经典的有插入日志、性能测试、事务处理等。在实际应用中也经常会用到装饰器。这篇文章来简单谈一下装饰器的实现方式。

二、闭包

  因为装饰器是基于闭包来实现的,这里简单说下Python的闭包。看下面的代码:

def outer():
var = 3 def inner():
print(var) return inner func = outer()
# 注意这个是func是outer返回的,所以它是inner函数 func() # 3 执行inner函数 print(func) # <function outer.<locals>.inner at 0x1022059d8>

我们知道一个函数可以调用其父级作用域的变量,所以函数inner可以打印变量var,函数也可以接收函数和返回函数,这样的函数称作高阶函数,在上面的代码中outer函数就是一个高阶函数。定义好函数之后,首先我们执行outer,并将返回值赋一个变量,因为我们的outer函数返回的也是一个函数,所以这个变量是可以执行的。

三、装饰器的实现

import time

def outer(f):

    def inner(x):
start_time = time.time()
f(x)
end_time = time.time()
amount_time = end_time - start_time
print(amount_time) return inner def foo(x):
print(x)
time.sleep(2) foo = outer(foo) foo("a")

上面的这段代码是否似曾相识?对,就是我们写的闭包。这段代码中,我们定义了一个普通的函数foo,可以理解为我们的业务函数,又定义了一个高阶函数outer,这个高阶函数可以理解为我们给业务函数添加的其他功能。这里是简单的业务时间统计。首先,我们在foo函数中需要将用户传的值进行操作,在实现之后,增加了一个统计时间的需求,这个时候我们的代码已经是完善的代码了,会尽量的避免源代码的修改的。这个时候使用装饰器来完成这个要求是很好的选择。我们先定义一个高阶函数outer,把我们的业务函数当做变量传递给outer,然后给outer的返回值赋一个变量,这个变量名要和我们的业务函数名字一样:foo,这样做是为了尽量少的变更源代码,这样我们在执行foo的时候就会变成执行outer中的inner函数,在inner函数中又执行了我们的业务函数。虽然实现了我们的要求,但是这样看起来代码会很乱,别着急,Python也为我们提供了一种简单的写法:

import time

def outer(f):

    def inner(x):
start_time = time.time()
f(x)
end_time = time.time()
amount_time = end_time - start_time
print(amount_time) return inner @outer
def foo(x):
print(x)
time.sleep(2) foo("a")

就是通过一个“@”符号加上我们的装饰器函数名就实现了,这样看起来是不是会好点呢?上面的代码只是为了演示装饰器的实现。装饰器还有很多种使用方法。

四、装饰器的使用

4.1 不带参数的装饰器

不带参数的装饰器非常简单:

def deco(func):
"""无参数调用decorator声明时必须有一个参数,这个参数将接收要装饰的方法"""
print "before myfunc() called."
func()
print "after myfunc() called."
return func @deco
def myfunc():
print " myfunc() called." myfunc()
myfunc()

定义好装饰器后,就可以通过@语法来使用了在函数的定义前调用@+装饰器函数名,即可使用。上面这个装饰器在使用的时候有一个问题,即只在第一次被调用,并且原来的函数多执行一次。执行输出如下:

before myfunc() called.
myfunc() called.
after myfunc() called.
myfunc() called. --函数多执行一次的输出
myfunc() called. --第二次调用,装饰器不生效

要保证新函数每次被调用,使用下面的方法来定义装饰器

def deco(func):
"""无参数调用decorator声明时必须有一个参数,这个参数将接收要装饰的方法"""
def _deco():
print "before myfunc() called."
func()
print "after myfunc() called."
#return func 不需要返回func
retrun _deco
@deco
def myfunc():
print " myfunc() called."
return 'OK' myfunc()
myfunc()

函数输出如下:

before myfunc() called.
myfunc() called.
after myfunc() called.
before myfunc() called.
myfunc() called.
after myfunc() called.

这样可以看到,装饰器每次都得到了调用。

4.2带参数的函数进行装饰器

def deco(func):
def _deco(a, b):
print("before myfunc() called.")
ret = func(a, b)
print(" after myfunc() called. result: %s" % ret)
return ret
return _deco @deco
def myfunc(a, b):
print(" myfunc(%s,%s) called." % (a, b))
return a + b myfunc(3, 4)

输出:

before myfunc() called.
myfunc() called.
After myfunc() called. result: 7

内嵌函数的形参和返回值与原函数相同,装饰函数返回内嵌包装函数。

4.3装饰器带参数

def decoWithArgs(arg):
"""由于有参数的decorator函数在调用时只会使用应用时的参数而不接收被装饰的函数做为参数,
所以必须返回一个decorator函数, 由它对被装饰的函数进行封装处理"""
  def newDeco(func): #定义一个新的decorator函数
  def replaceFunc(): #在decorator函数里面再定义一个内嵌函数,由它封装具体的操作
  print "Enter decorator %s" %arg #进行额外操作
  func() #对被装饰函数进行调用
  return replaceFunc
  return newDeco #返回一个新的decorator函数 @decoWithArgs("demo")
def MyFunc(): #应用@decoWithArgs修饰的方法
print "Enter MyFunc" MyFunc() #调用被装饰的函数

输出:
nter decorator demo
Enter MyFunc

这个情形适用于原来的函数没有参数,新增加打印的情况。常见适用的地方是增加函数的打印日志。

4.4对参数数量不确定的函数进行装饰

下面的例子是一个邮件异步发送的例子,函数的参数数据部确定,装饰器实现了对于邮件发送函数的异步发送。

from threading import Thread

def async(f):
def wrapper(*args, **kwargs):
thr = Thread(target = f, args = args, kwargs = kwargs)
thr.start()
return wrapper @async
def send_async_email(msg):
mail.send(msg) def send_email(subject, sender, recipients, text_body, html_body):
msg = Message(subject, sender = sender, recipients = recipients)
msg.body = text_body
msg.html = html_body
send_async_email(msg)

并且这个装饰器可以适用一切需要异步处理的功能,做到非常好的代码复用。

4.5让装饰器带类参数

class locker:
def __init__(self):
print("locker.__init__() should be not called.") @staticmethod
def acquire():
print("locker.acquire() called.(这是静态方法)") @staticmethod
def release():
print(" locker.release() called.(不需要对象实例)") def deco(cls):
'''cls 必须实现acquire和release静态方法'''
def _deco(func):
def __deco():
print("before %s called [%s]." % (func.__name__, cls))
cls.acquire()
try:
return func()
finally:
cls.release()
return __deco
return _deco @deco(locker)
def myfunc():
print(" myfunc() called.") myfunc()
myfunc()

输出为:

before myfunc called [__main__.locker].
locker.acquire() called.(this is staticmethon)
myfunc() called.
locker.release() called.(do't need object ) before myfunc called [__main__.locker].
locker.acquire() called.(this is staticmethon)
myfunc() called.
locker.release() called.(do't need object )

装饰器总结

当我们对某个方法应用了装饰方法后, 其实就改变了被装饰函数名称所引用的函数代码块入口点,使其重新指向了由装饰方法所返回的函数入口点。由此我们可以用decorator改变某个原有函数的功能,添加各种操作,或者完全改变原有实现。

本文中装饰器使用场景及总结转自:http://www.cnblogs.com/StitchSun/p/4600835.html

浅谈Python装饰器的更多相关文章

  1. 小谈python装饰器及numba的基本使用

    1. 预热知识 要理解python中的装饰器,就要明白在python中,函数是一种特殊类型的变量,可以作为参数传递给函数,也可以作为返回值返回.比如下面的代码,就是 str_1 作为参数传递给 str ...

  2. 开发技术--浅谈Python函数

    开发|浅谈Python函数 函数在实际使用中有很多不一样的小九九,我将从最基础的函数内容,延伸出函数的高级用法.此文非科普片~~ 前言 目前所有的文章思想格式都是:知识+情感. 知识:对于所有的知识点 ...

  3. python装饰器方法

    前几天向几位新同事介绍项目,被问起了@login_required的实现,我说这是django框架提供的装饰器方法,验证用户是否登录,只要这样用就行了,因为自己不熟,并没有做过多解释. 今天查看dja ...

  4. Python装饰器模式学习总结

    装饰器模式,重点在于装饰.装饰的核心仍旧是被装饰对象. 类比于Java编程的时候的包装模式,是同样的道理.虽然概念上稍有不同但是原理上还是比较相近的.下面我就来谈一谈我对Python的装饰器的学习的一 ...

  5. 理解 Python 装饰器看这一篇就够了

    讲 Python 装饰器前,我想先举个例子,虽有点污,但跟装饰器这个话题很贴切. 每个人都有的内裤主要功能是用来遮羞,但是到了冬天它没法为我们防风御寒,咋办?我们想到的一个办法就是把内裤改造一下,让它 ...

  6. Python 装饰器入门(上)

    翻译前想说的话: 这是一篇介绍python装饰器的文章,对比之前看到的类似介绍装饰器的文章,个人认为无人可出其右,文章由浅到深,由函数介绍到装饰器的高级应用,每个介绍必有例子说明.文章太长,看完原文后 ...

  7. Python装饰器基础

    一.Python装饰器引入 讲 Python 装饰器前,我想先举个例子,虽有点污,但跟装饰器这个话题很贴切. 每个人都有的内裤主要功能是用来遮羞,但是到了冬天它没法为我们防风御寒,咋办?我们想到的一个 ...

  8. Python 装饰器填坑指南 | 最常见的报错信息、原因和解决方案

    本文为霍格沃兹测试学院学员学习笔记. Python 装饰器简介 装饰器(Decorator)是 Python 非常实用的一个语法糖功能.装饰器本质是一种返回值也是函数的函数,可以称之为“函数的函数”. ...

  9. Python装饰器,Python闭包

    可参考:https://www.cnblogs.com/lianyingteng/p/7743876.html suqare(5)等价于power(2)(5):cube(5)等价于power(3)(5 ...

随机推荐

  1. 江西财经大学第二届程序设计竞赛同步赛 H大时钟 (扩展欧几里得)

    链接:https://ac.nowcoder.com/acm/contest/635/H来源:牛客网 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 262144K,其他语言52428 ...

  2. github+jekyll个人博客搭建

    Thanks to https://blog.csdn.net/Hanghang_/article/details/78944672 跟着这个博客一步步搭建,从jekyll官网上找到自己喜欢的主题. ...

  3. C语言的第一堂课

    感觉茂哥讲了很多,但是有些输入的语句还是没能记住 刚讲的都是概念,看来需要看一下表格,以及C语言的基础 有些意思还不能够理解 略显尴尬 回去把C语言书的各种概念理解一下……

  4. pandas技巧两则——列内元素统计和列内元素排序

    更新:后来忽然发现有个cumcount()函数,支持正排倒排,所以以下说的那些基本都没啥用了. 最近做比赛线上无甚进展,所以先小小地总结遇到的一些困难和解决的方法,以防之后忘记.毕竟总是忙着大步赶路的 ...

  5. 为Nexus配置阿里云代理仓库【转】

    Nexus默认远程仓库为https://repo1.maven.org/maven2/ 慢死,还常连不上. 可以添加阿里云代理仓库 URL:http://maven.aliyun.com/nexus/ ...

  6. Open vSwitch系列之二 安装指定版本ovs

    在ovs学习过程中,如果自己想要安装一个ovs交换机其实一条简单的命令 apt  install openvswitch 就可以了,但是这种方法只能安装低版本的ovs.在特殊情况下需要安装指定版本,例 ...

  7. [算法竞赛入门经典]Message Decoding,ACM/ICPC World Finals 1991,UVa213

    Description Some message encoding schemes require that an encoded message be sent in two parts. The ...

  8. ueditor接入秀米编辑器

    秀米编辑器用来编辑微信页面很方便,功能也比较强大.秀米提供了第三方编辑器接入的功能,接入方法可以参照官网示例:http://hgs.xiumi.us/uedit/ 但是这里有几点要注意: 1. 示例中 ...

  9. Redis 简介与命令操作

    redis 是 key-value 的数据,所以每个数据都是一个键值对,键的类型是字符串: 值的类型分为五种:string.hash.list.set(集合).zset(有序集合). 数据操作的全部命 ...

  10. Spring Boot系列(二) Spring Boot 之 REST

    Rest (Representational Stat Transer) 是一种软件架构风格. 基础理论 架构特性 性能 可伸缩 简化的统一接口 按需修改 组件通信透明 可移植 可靠性 架构约束 C/ ...