Python装饰器笔记
DRY(Don't Repeat Yourself)原则:
一般是指在写代码的时候尽量避免重复的实现。违反DRY原则导致的坏处很容易理解,例如维护困难,修改时一旦遗漏就会产生不易察觉的问题。
一.函数装饰器
1.从Python内层函数说起
使用内层函数的三个好处
- 封装
- 贯彻DRY原则
- 闭包和工厂函数
1.封装
def outer(num1):
def inner_increment(num1): # hidden from outer code
return num1 + 1
num2 = inner_increment(num1)
print(num1, num2)
inner_increment(10) #不能正确运行
# outer(10) #可以正常运行
这样把内层函数从全局作用域隐藏起来,不能直接调用。
使用这种设计模式的一个主要优势在于:在外部函数中对全部参数执行了检查,你可以在内部函数中跳过全部的检查过程。
2.贯彻DRY原则
比如,你可能写了一个函数用来处理文件,并且你希望它既可以接受一个打开文件对象或是一个文件名:
def process(file_name):
def do_stuff(file_process):
for line in file_process:
print(line)
if isinstance(file_name, str):
with open(file_name, 'r') as f:
do_stuff(f)
else:
do_stuff(file_name)
3.闭包和工厂函数
闭包无非是使内层函数在调用时记住它当前环境的状态。初学者经常认为闭包就是内层函数,而且实际上它是由内层函数导致的。闭包在栈上“封闭”了局部变量,使其在栈创建执行结束后仍然存在。
def generate_power(number):
# define the inner function ...
def nth_power(power):
return number ** power
# ... which is returned by the factory function
return nth_power
>>raise_two = generate_power(2)
>>print(raise_two(7))
128
外层函数接受一个参数number=2,然后生成一个nth_power()函数,该函数只接受一个单一的参数power,其中包含number=2
返回的函数被赋值给变量raise_two
,我们可以通过raise_two
来调用函数并传递变量。
换句话说,闭包函数“初始化”了nth_power()函数并将其返回。现在无论你何时调用这个新返回的函数,它都会去查看其私有的快照,也就是包含number=2的那一个。
2.装饰器
「装饰器」是一个很著名的设计模式,经常被用于有切面需求的场景,较为经典的有插入日志、性能测试、事务处理等
装饰器其实就是一个工厂函数,它接受一个函数为参数,然后返回一个新函数,其闭包中包含了原函数
1.简单装饰器
def deco(func):
def wrapper():
print "start"
func() #调用函数
print "end"
return wrapper
@deco
def myfun():
print "run"
myfun()
由于装饰器函数返回的是原函数的闭包wrapper,实际上被装饰后的函数就是wrapper,其运行方式就和wrapper一样。
相当于myfun=deco(myfun)
2.装饰一个需要传递参数的函数
def deco(func):
def wrapper(param):
print "start"
func(param)
print "end"
return wrapper
@deco
def myfun(param):
print "run with param %s"%(param)
myfun("something")
这种情况下,仍然返回wrapper,但是这个wrapper可以接受一个参数,因此这样的装饰器只能作用于接受一个参数的函数
3.装饰任意参数的函数
def deco(func):
def warpper(*args,**kw):
print "start"
func(*args,**kw)
print "end"
return warpper
@deco
def myfun1(param1):
print "run with param %s"%(param1)
@deco
def myfun2(param1,param2):
print "run with param %s and %s"%(param1,param2)
myfun1("something")
myfun2("something","otherthing")
# start
# run with param something
# end
# start
# run with param something and otherthing
# end
两个函数可以被同样一个装饰器所装饰
4.带参数的装饰器
装饰器接受一个函数作为参数,这个毋庸置疑。但是有时候我们需要装饰器接受另外的参数。此时需要再加一层函数,实际上是定义了一个生成装饰器的工厂函数,调用它,搭配需要的参数,来返回合适的装饰器。
def log(text):
def deco(func):
def wrapper(*args,**kw):
print text
func(*args,**kw)
print text + " again"
return wrapper
return deco
@log("hello")
def myfun(message):
print message
myfun("world")
# hello
# world
# hello again
这里分两步
log=log("hello")
,把返回的deco函数赋值给log,此时log相当于其包含text=“hello”的闭包myfun=log(myfun)
,相当于把myfun传入了deco函数,并且返回wrapper,并赋值给myfun,此时myfun相当于其装饰后的闭包。
整体来看是myfun=log("hello")(myfun)
5.装饰器带类参数
# -*- coding:gbk -*-
'''''示例8: 装饰器带类参数'''
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() # -*- coding:gbk -*-
'''''示例8: 装饰器带类参数'''
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()
关于wrapper的返回值
上面的代码中,我们的wrapper函数都没有返回值,而是在wrapper中直接调用了func函数,这么做的目的是要在函数运行前后打印一些字符串。而func函数本事也只是打印字符串而已。
但是这么做有时会违背func函数的初衷,比如func函数确实是需要返回值的,那么其装饰后的函数wrapper也应该把值返回。
我们看这样一段函数:
def deco(func):
def warpper(*args,**kw):
print "start"
func(*args,**kw)#直接调用,无返回值
print "end"
return warpper
@deco
def myfun(param):
return 2+param
sum=myfun(2) #期望纪录返回值并打印
print sum
结果,并没有返回值
>>
start
end
None
因此我们需要wrapper把函数结果返回:
def deco(func):
def warpper(*args,**kw):
print "start"
result=func(*args,**kw)#纪录结果
print "end"
return result #返回
return warpper
@deco
def myfun(param):
return 2**param
sum=myfun(2) #这里其实是sum=result
print sum
当然,如果不是为了在func前后打印字符串,也可以把func直接返回
一个实际例子:统计函数执行时间
from time import time,sleep
def timer(func):
def warpper(*args,**kw):
tic=time()
result=func(*args,**kw)
toc=time()
print "%f seconds has passed"%(toc-tic)
return result
return warpper
@timer
def myfun():
sleep(2)
return "end"
print myfun()
# 2.005432 seconds has passed
# end
关于装饰器装饰过程中函数名称的变化
当装饰器装饰函数并返回wrapper后,原本myfun的__name__
就改变了
from time import time,sleep
def timer(func):
def warpper(*args,**kw):
tic=time()
result=func(*args,**kw)
toc=time()
print func.__name__
print "%f seconds has passed"%(toc-tic)
return result
return warpper
@timer
def myfun():
sleep(2)
return "end"
myfun()
print myfun.__name__ #wrapper
# myfun
# 2.003399 seconds has passed
# warpper
这样对于一些依赖函数名的功能就会失效,而且也不太符合逻辑,毕竟wrapper对于我们只是一个中间产物
from time import time,sleep
import functools
def timer(func):
@functools.wraps(func)
def warpper(*args,**kw):
tic=time()
result=func(*args,**kw)
toc=time()
print func.__name__
print "%f seconds has passed"%(toc-tic)
return result
return warpper
@timer
def myfun():
sleep(2)
return "end"
myfun()
print myfun.__name__ #wrapper
# myfun
# 2.003737 seconds has passed
# myfun
导入模块import functools
,并且用@functools.wraps(func)
装饰wrapper即可
3.Flask中的@app.route()
装饰器
Things which aren't magic - Flask and @app.route - Part 1
Things which aren't magic - Flask and @app.route - Part 2
class NotFlask():
def route(self, route_str):
def decorator(f):
return f
return decorator
app = NotFlask()
@app.route("/")
def hello():
return "Hello World!"
route是NotFlask类的一个方法,并且其实际上是一个装饰器工厂,这里我们并没有装饰我们的函数,装饰器仅仅返回了函数的引用而没有装饰它。
class NotFlask():
def __init__(self):
self.routes = {}
def route(self, route_str):
def decorator(f):
self.routes[route_str] = f
return f
return decorator
app = NotFlask()
@app.route("/")
def hello():
return "Hello World!"
现在给装饰器初始化一个字典,在我们传入参数生产装饰器route的时候,把函数存入字典响应位置,key为url字符串,value为相应函数。
不过此时,我们并不能访问这个内部的视图函数,我们需要一个方法来获取相应的视图函数。
class NotFlask():
def __init__(self):
self.routes = {}
def route(self, route_str):
def decorator(f):
self.routes[route_str] = f
return f
return decorator
def serve(self, path):
view_function = self.routes.get(path)#获取相应函数
if view_function:
return view_function()#返回函数
else:
raise ValueError('Route "{}"" has not been registered'.format(path))
app = NotFlask()
@app.route("/")
def hello():
return "Hello World!"
然后我们可以这样,通过url字符串来访问相应的视图函数
app = NotFlask()
@app.route("/")
def hello():
return "Hello World!"
print app.serve("/")
#>>Hello World!
小结
Flask路由装饰器的主要功能,就是绑定url到相应的函数。
(如何访问视图函数其实是HTTP服务器的一部分)
当然,目前的url绑定还太死板,我们需要url能够加入可变参数
下面我们要实现从url中识别出参数
app = Flask(__name__)
@app.route("/hello/<username>")
def hello_user(username):
return "Hello {}!".format(username)
首先我们要利用命名捕获组,从url中识别参数
route_regex = re.compile(r'^/hello/(?P<username>.+)$')
match = route_regex.match("/hello/ains")
print match.groupdict()
当然,我们需要一个方法来把输入的url转化为相应的正则表达式
def build_route_pattern(route):
route_regex = re.sub(r'(<\w+>)', r'(?P\1.+)', route)
return re.compile("^{}$".format(route_regex))
print build_route_pattern('/hello/<username>')
class NotFlask():
def __init__(self):
self.routes = []
# Here's our build_route_pattern we made earlier
@staticmethod
def build_route_pattern(route):
route_regex = re.sub(r'(<\w+>)', r'(?P\1.+)', route)
return re.compile("^{}$".format(route_regex))
def route(self, route_str):
def decorator(f):
# Instead of inserting into a dictionary,
# We'll append the tuple to our route list
route_pattern = self.build_route_pattern(route_str)
self.routes.append((route_pattern, f))
return f
return decorator
与之前的代码不同,字典被移除了,取而代之的是一个列表,然后我们把生成的正则表达式和相应的函数作为元组放到列表里。
同样,我们需要一个方法来返回视图函数,当然,还有捕获匹配组的字典,我们需要它来传递正确的参数
def get_route_match(path):
for route_pattern, view_function in self.routes:
m = route_pattern.match(path)
if m:
return m.groupdict(), view_function
return None
最终结果:
class NotFlask():
def __init__(self):
self.routes = []
@staticmethod
def build_route_pattern(route):
route_regex = re.sub(r'(<\w+>)', r'(?P\1.+)', route)
return re.compile("^{}$".format(route_regex))
def route(self, route_str):
def decorator(f):
route_pattern = self.build_route_pattern(route_str)
self.routes.append((route_pattern, f))
return f
return decorator
def get_route_match(self, path):
for route_pattern, view_function in self.routes:
m = route_pattern.match(path)
if m:
return m.groupdict(), view_function
return None
def serve(self, path):
#查找和path匹配的视图函数以及捕获组字典
route_match = self.get_route_match(path)
if route_match:
kwargs, view_function = route_match
return view_function(**kwargs)#捕获组字典作为函数参数
else:
raise ValueError('Route "{}"" has not been registered'.format(path))
使用方法:
app = NotFlask()
@app.route("/hello/<username>")
def hello_user(username):
return "Hello {}!".format(username)
print app.serve("/hello/ains")
>>Hello ains!
小结
装饰阶段:
- 装饰器工厂route接受url字符串,生成一个合适的装饰器
- 装饰器装饰视图函数,生成url字符串对应的正则表达式模板,连同视图函数组成元组,存放在列表中。然后把函数返回。
调用阶段:
- app.serve并传入url的时候,首先在列表中查找,依次进行匹配,是否有符合该模式的路径和视图函数
- 有则返回相应获取捕获组字典和视图函数
- 将字典作为参数,返回该视图函数的运行结果。
原文:http://www.jianshu.com/p/1e2394733e77
Python装饰器笔记的更多相关文章
- Python 装饰器(笔记,非原创)
定义:本质是函数,为其他函数添加附加功能原则:1.不能修改被装饰的函数的源代码 2.不能修改被装饰的函数的调用方式知识储备: 1.函数即“变量” 2.高阶函数 ...
- Python 装饰器笔记
一.装饰器无参数 1.原函数无参数 def wrap_in_tag_b(fn): # wrap_in_tag_b 是真正的装饰器 def wrapped(): return "<b&g ...
- Python 装饰器学习心得
最近打算重新开始记录自己的学习过程,于是就捡起被自己废弃了一年多的博客.这篇学习笔记主要是记录近来看的有关Python装饰器的东西. 0. 什么是装饰器? 本质上来说,装饰器其实就是一个特殊功能的函数 ...
- Python 装饰器填坑指南 | 最常见的报错信息、原因和解决方案
本文为霍格沃兹测试学院学员学习笔记. Python 装饰器简介 装饰器(Decorator)是 Python 非常实用的一个语法糖功能.装饰器本质是一种返回值也是函数的函数,可以称之为“函数的函数”. ...
- 关于python装饰器
关于python装饰器,不是系统的介绍,只是说一下某些问题 1 首先了解变量作用于非常重要 2 其次要了解闭包 def logger(func): def inner(*args, **kwargs) ...
- python装饰器通俗易懂的解释!
1.python装饰器 刚刚接触python的装饰器,简直懵逼了,直接不懂什么意思啊有木有,自己都忘了走了多少遍Debug,查了多少遍资料,猜有点点开始明白了.总结了一下解释得比较好的,通俗易懂的来说 ...
- Python 装饰器学习
Python装饰器学习(九步入门) 这是在Python学习小组上介绍的内容,现学现卖.多练习是好的学习方式. 第一步:最简单的函数,准备附加额外功能 1 2 3 4 5 6 7 8 # -*- c ...
- python 装饰器修改调整函数参数
简单记录一下利用python装饰器来调整函数的方法.现在有个需求:参数line范围为1-16,要求把9-16的范围转化为1-8,即9对应1,10对应2,...,16对应8. 下面是例子: def fo ...
- python 装饰器学习(decorator)
最近看到有个装饰器的例子,没看懂, #!/usr/bin/python class decorator(object): def __init__(self,f): print "initi ...
随机推荐
- UVA 11983 Weird Advertisement --线段树求矩形问题
题意:给出n个矩形,求矩形中被覆盖K次以上的面积的和. 解法:整体与求矩形面积并差不多,不过在更新pushup改变len的时候,要有一层循环,来更新tree[rt].len[i],其中tree[rt] ...
- DOTween文档
前言 DOTween现在还处于 alpha,所以还有一些缺失的功能(如路径插件,附加回调和其它的tween选项),这个文档在不久的将来可能会更新. DoTween:0.8.2.00 官方文档:http ...
- java的IO操作之--RandomAccessFile
目标: 1)掌握RandomAccessFile类的作用. 2)用RandomAccessFile读取指定位置的数据. 具体内容 RandomAccessFile类的主要功能是完成随机读取功能,可以读 ...
- React Native iOS环境搭建
前段时间React Native for Android发布,感觉React Native会越来越多的公司开始研究.使用.所以周六也抽空搭建了iOS的开发环境,以便以后利用空闲的时间能够学习一下. 废 ...
- 第五章 使用 Bootstrap Typeahead 组件(百度下拉效果)
推荐链接:http://www.cnblogs.com/haogj/p/3376874.html UnderScore官网:http://underscorejs.org/ 参考文档:http://w ...
- 使用地址栏访问CXF Webservice写法
/* * 通过url调用 * http://localhost:8080/EFP/webService/TestWebservice/testOut/arg0/liuyx */ http://loca ...
- [转]Windows网络编程学习-面向连接的编程方式
直接附上原文链接:windows 网络编程学习-面向连接的编程方式
- 使用log4j将日志写入数据库并发送邮件
参考: 快速了解Log4J 1.log4j的初始配置 参考该问的配置即可完整的实现写入数据库及发送邮件的功能 a.写入数据库需要配置相应的jar包,数据库类型不同,请使用指定的数据库配置,该文仅限于o ...
- Linux常用指令---tail | head(查看文件)
tail 命令从指定点开始将文件写到标准输出.使用tail命令的-f选项可以方便的查阅正在改变的日志文件,tail -f filename会把filename里最尾部的内容显示在屏幕上,并且不但刷新, ...
- java系列: 对不起,JavaFX——Java 8目前还不能救你(zz)
JavaFX 是SUN公司在2007年JavaOne大会上首次对外公布的以Java为基础构建的富客户端平台,更让开发者印象比较深刻的则是其背后的JavaFX开发团队,仅仅在两年的时间就从1.0版本完善 ...