浅谈Python装饰器
一、概念
装饰器是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装饰器的更多相关文章
- 小谈python装饰器及numba的基本使用
1. 预热知识 要理解python中的装饰器,就要明白在python中,函数是一种特殊类型的变量,可以作为参数传递给函数,也可以作为返回值返回.比如下面的代码,就是 str_1 作为参数传递给 str ...
- 开发技术--浅谈Python函数
开发|浅谈Python函数 函数在实际使用中有很多不一样的小九九,我将从最基础的函数内容,延伸出函数的高级用法.此文非科普片~~ 前言 目前所有的文章思想格式都是:知识+情感. 知识:对于所有的知识点 ...
- python装饰器方法
前几天向几位新同事介绍项目,被问起了@login_required的实现,我说这是django框架提供的装饰器方法,验证用户是否登录,只要这样用就行了,因为自己不熟,并没有做过多解释. 今天查看dja ...
- Python装饰器模式学习总结
装饰器模式,重点在于装饰.装饰的核心仍旧是被装饰对象. 类比于Java编程的时候的包装模式,是同样的道理.虽然概念上稍有不同但是原理上还是比较相近的.下面我就来谈一谈我对Python的装饰器的学习的一 ...
- 理解 Python 装饰器看这一篇就够了
讲 Python 装饰器前,我想先举个例子,虽有点污,但跟装饰器这个话题很贴切. 每个人都有的内裤主要功能是用来遮羞,但是到了冬天它没法为我们防风御寒,咋办?我们想到的一个办法就是把内裤改造一下,让它 ...
- Python 装饰器入门(上)
翻译前想说的话: 这是一篇介绍python装饰器的文章,对比之前看到的类似介绍装饰器的文章,个人认为无人可出其右,文章由浅到深,由函数介绍到装饰器的高级应用,每个介绍必有例子说明.文章太长,看完原文后 ...
- Python装饰器基础
一.Python装饰器引入 讲 Python 装饰器前,我想先举个例子,虽有点污,但跟装饰器这个话题很贴切. 每个人都有的内裤主要功能是用来遮羞,但是到了冬天它没法为我们防风御寒,咋办?我们想到的一个 ...
- Python 装饰器填坑指南 | 最常见的报错信息、原因和解决方案
本文为霍格沃兹测试学院学员学习笔记. Python 装饰器简介 装饰器(Decorator)是 Python 非常实用的一个语法糖功能.装饰器本质是一种返回值也是函数的函数,可以称之为“函数的函数”. ...
- Python装饰器,Python闭包
可参考:https://www.cnblogs.com/lianyingteng/p/7743876.html suqare(5)等价于power(2)(5):cube(5)等价于power(3)(5 ...
随机推荐
- Go中的iota
当时在学习Iota这个知识点的时候仅仅是一笔掠过,比如这种 const( a=iota b c ) 一眼看出他怎么使用的时候就觉得自己已经懂得了 再到后来看到这样的例子 const( a = 5*io ...
- 【MyBatis】Mapper XML 文件
Mapper XML文件 MyBatis 的真正强大在于它的映射语句,也是它的魔力所在.由于它的异常强大,映射器的 XML 文件就显得相对简单.如果拿它跟具有相同功能的 JDBC 代码进行对比,你会立 ...
- iptables防火墙规则积累
1.内网IP地址端口映射: 通过访问本机地址的3389端口,可以访问到192.168.1.139的远程桌面服务: -A PREROUTING -p tcp -m tcp --dport 3389 ...
- Flexbox(弹性盒模型)完全指南
Flexbox(弹性盒模型)布局完全指南 Github:sueRimn 来源:A guide to Flexbox 这个指南讲诉了flexbox的所有内容,重点介绍了父元素(flex容器)和子元素(f ...
- Docker 介绍及基础命令
Docker 简介 Docker 是一个开源项目,诞生于 2013 年初,最初是 dotCloud 公司内部的一个业余项目.它基于 Google 公司推出的 Go 语言实现. 项目后来加入了 Linu ...
- 洛谷P3327 约数个数和 结论+莫比乌斯反演
原题 就是让你求\(\sum\limits_{i=1}\sum\limits_{j=1}d(ij)\)(其中\(d(x)\)表示\(x\)的因数个数) 首先有引理(然而并没有证明): \(d(ij)= ...
- BZOJ2287 消失之物
这题貌似是个权限题qwq,我是用离线题库+本地数据包测的 题目大意: 给你\(n\)个体积分别为\(w[i]\)的物品和容积\(m\),问你将每一件物品分别去掉之后,拼出\(1\)~\(m\)中每一个 ...
- redis connection refused: connect 启动失败
先到redis解压包下执行 ./redis-server redis.conf 再连接 redis-cli
- jQuery使用():Deferred有状态的回调列表(含源码)
deferred的功能及其使用 deferred的实现原理及模拟源码 一.deferred的功能及其使用 deferred的底层是基于callbacks实现的,建议再熟悉callbacks的内部机制前 ...
- Java方法参数的传递方式
程序设计语言中,将参数传递给方法(或函数)有两种方法.按值传递(call by value)表示方法接受的是调用者提供的值:按引用调用(call by reference)表示方法接受的是调用者提供的 ...