python函数式编程之装饰器(一)
1.开放封闭原则
简单来说,就是对扩展开放,对修改封闭
在面向对象的编程方式中,经常会定义各种函数。
一个函数的使用分为定义阶段和使用阶段,一个函数定义完成以后,可能会在很多位置被调用
这意味着如果函数的定义阶段代码被修改,受到影响的地方就会有很多,此时很容易因为一个小地方的修改而影响整套系统的崩溃,
所以对于现代程序开发行业来说,一套系统一旦上线,系统的源代码就一定不能够再改动了。
然而一套系统上线以后,随着用户数量的不断增加,一定会为一套系统扩展添加新的功能。
此时,又不能修改原有系统的源代码,又要为原有系统开发增加新功能,这就是程序开发行业的开放封闭原则,这时就要用到装饰器了。
2.什么是装饰器??
装饰器,顾名思义,就是装饰,修饰别的对象的一种工具。
所以装饰器可以是任意可调用的对象,被装饰的对象也可以是任意可调用对象
3.装饰器的作用
在不修改被装饰对象的源代码以及调用方式的前提下为被装饰对象添加新功能
原则:
1.不修改被装饰对象的源代码
2.不修改被装饰对象的调用方式
目标:
为被装饰对象添加新功能
4.装饰器的定义和使用
来看下面的代码:
import time
import random
def index():
time.sleep(random.randrange(1,5))
print("welcome to index page")
index()
index函数的作用是程序在随机睡眠1到5秒之后,打印一句话
现在想为index函数添加一个新功能:统计index函数的运行时间,该怎么做呢??
修改index函数如下:
import time
import random
def index():
start_time=time.time()
time.sleep(random.randrange(1,5))
print("welcome to index page")
end_time=time.time()
print("cost time: %s" %(end_time - start_time))
index()
运行程序,执行结果如下:
welcome to index page
cost time: 2.000999927520752
可以看到,为index函数添加新功能确实实现了,但是却违反了开放封闭原则。
在符合开放封闭原则的前提下,如果想为index函数添加新功能,此时就要使用装饰器了
修改代码
import time
import random
def index():
time.sleep(random.randrange(1,5))
print("welcome to index page")
def timmer():
def inner():
start_time=time.time()
index()
end_time=time.time()
print("run time: %s " %(end_time-start_time))
return inner
f=timmer()
f()
运行程序,查看执行结果
welcome to index page
run time: 1.0
从程序执行结果可以看出,index函数的运行时间已经被统计出来了
但是查看源码可以知道,index函数的源码确实没有被修改,但是index的调用方式被修改了
而且还有一个问题就是,timmer这个装饰器只能被用来装饰index这个函数,如果以后想统计别的函数的运行时间,又要重新定义别的装饰器,这样也太不灵活了。
修改上面的代码
import time
import random
def timmer(func):
def inner():
start_time=time.time()
func()
end_time=time.time()
print("run time: %s " %(end_time-start_time))
return inner
def index():
time.sleep(random.randrange(1,5))
print("welcome to index page")
index=timmer(index)
index()
运行程序,查看程序执行结果
welcome to index page
run time: 4.0
可以看到,index函数的源代码没有被修改,index函数的调用方式也没有改变,但是依然为index函数添加了统计时间的功能,这里使用的就是装饰器了。
来分析下上面代码的执行流程:
1.导入time和random模块,定义index函数和timmer函数
2.把原始的index函数的内存地址作为参数传给timmer函数。
3.timmer函数内部嵌套定义一个函数inner,然后返回inner函数的内存地址
4.timmer函数执行完成,返回timmer函数的内部函数inner的内存地址,然后把inner的内存地址赋值给index变量
5.index是inner函数的内存地址,index变量加括号运行,实际上就是在运行inner函数
6.运行inner函数,定义程序开始时间。
7.执行timmer函数的变量func,在第2步知道,func这个变量就是index的内存地址,所以这里实际上是执行被装饰过后的index函数
8.index函数执行完成,定义程序的终止时间
9.统计并打印整个程序的执行过程中所花费的时间
这就是装饰器装饰index函数的执行流程
5.装饰器的简化使用
现在我又有另外一个函数home,现在我也想统计home函数的运行时间,可以把代码修改如下
import time
import random
def timmer(func):
def inner():
start_time=time.time()
func()
end_time=time.time()
print("run time: %s " %(end_time-start_time))
return inner
def index():
time.sleep(random.randrange(1,5))
print("welcome to index page")
def home():
time.sleep(random.randrange(1,5))
print("welcome to home page")
index=timmer(index)
index()
home=timmer(home)
home()
运行程序,执行结果如下
welcome to index page
run time: 3.0
welcome to home page
run time: 4.0
可以看到,每次调用统计程序运行时间的装饰器timmer,都要先把被调用的函数的函数名作为参数传给timmer装饰器
然后再把timmer装饰器的执行结果赋值给被调用的函数名本身,最后才能调用被装饰的函数,太麻烦了有没有??
其实python中的装饰器可以简化成下面的格式
import time
import random
def timmer(func):
def inner():
start_time=time.time()
func()
end_time=time.time()
print("run time: %s " %(end_time-start_time))
return inner
@timmer
def index():
time.sleep(random.randrange(1,5))
print("welcome to index page")
@timmer
def home():
time.sleep(random.randrange(1,5))
print("welcome to home page")
index()
home()
程序执行结果
welcome to index page
run time: 2.0
welcome to home page
run time: 4.0
可以看出,使用@加装饰器名添加到被装饰对象的上方的方式也可以为一个函数添加装饰器中定义的功能
6.多个装饰器的定义与调用
在上面的例子里,定义并调用了一个统计程序运行时间的装饰器timmer,
如果现在想为index函数添加一个用户认证的功能,可以定义一个名为auth的装饰器
import time
import random
def auth(func):
def wrapper():
while True:
user=input("Input your username>>>:").strip()
pwd=input("Input your password>>>:").strip()
if user== "abcd" and pwd == "abcd1234":
print("login successful")
func()
break
else:
print("login error")
return wrapper
@auth
def index():
time.sleep(random.randrange(1,5))
print("welcome to index page")
index()
运行程序
Input your username>>>:abcd # 先输入错误的用户名和密码
Input your password>>>:1234
login error # 提示用户输入错误,登录失败
Input your username>>>:abcd # 让用户再次输入用户名和密码
Input your password>>>:abcd1234
login successful # 登录成功
welcome to index page # 执行index函数
从程序执行结果可以看出,用户登录密码验证的装饰器auth已经定义并被成功调用了
如果想为index函数添加用户认证的功能,又想统计index函数执行时间的功能,在使用装饰器的情况下该怎么调用呢
import time
import random
def timmer(func):
def inner():
start_time=time.time()
func()
end_time=time.time()
print("run time: %s " %(end_time-start_time))
return inner
def auth(func):
def wrapper():
while True:
user=input("Input your username>>>:").strip()
pwd=input("Input your password>>>:").strip()
if user== "abcd" and pwd == "abcd1234":
print("login successful")
func()
break
else:
print("login error")
return wrapper
@timmer
@auth
def index():
time.sleep(2)
print("welcome to index page")
index()
在上面的代码里,为index函数添加了两个装饰器,现在有一个问题,就是这两个装饰器究竟哪个先被调用,哪个后被调用呢??
来分析一下,
如果timmer装饰器先被调用,那么程序就会先执行timmer装饰器,然后再执行auth装饰器,提示输入用户名和密码,
这样一来timmer装饰器统计的时间就会包括输入用户名和密码的时间,这个时间会远远大于index函数睡眠的2秒种;
如果auth装饰器先被调用,timmer装饰器后被调用,那么timmer装饰器统计的运行时间就应该只包括index函数的执行时间值应该在2秒多一点点的时间范围内
运行程序,先输入错误的用户名和密码以使用程序的执行时间加长
Input your username>>>:abcd
Input your password>>>:abcd
login error
Input your username>>>:abcd
Input your password>>>:abcd1234
login successful
welcome to index page
run time: 12.759000062942505
从程序的执行结果可以知道,程序是先运行timmer装饰器,然后才运行auth装饰器,所以timmer统计的时间就包括了用户认证的时间,所以timmer统计到的程序运行时间远远大于index睡眠的2秒钟
所以这里得出一个结论:
当一个函数同时被两个装饰器装饰时,加上函数最上面的装饰器先执行,加在下面的装饰器先装饰
把上面例子里的timmer装饰器和auth装饰器位置互换一下
import time
import random
def timmer(func):
def inner():
start_time=time.time()
func()
end_time=time.time()
print("run time: %s " %(end_time-start_time))
return inner
def auth(func):
def wrapper():
while True:
user=input("Input your username>>>:").strip()
pwd=input("Input your password>>>:").strip()
if user== "abcd" and pwd == "abcd1234":
print("login successful")
func()
break
else:
print("login error")
return wrapper
@auth
@timmer
def index():
time.sleep(2)
print("welcome to index page")
index()
运行index函数,依然先输入错误的用户名和密码,增加用户认证的时间
Input your username>>>:abcd
Input your password>>>:abcd
login error
Input your username>>>:abcd
Input your password>>>:abcd1234
login successful
welcome to index page
run time: 2.0
可以看到,这次timmer统计到的时间只包含index函数的运行时间,不包含用户进行认证的时间
来分析一下上面例子中,index函数被timmer装饰器和auth装饰器装饰的代码装饰流程
@auth # index=auth(timmer(index))
@timmer # index=timmer(index)
def index():
time.sleep(2)
print("welcome to index page")
在上面得出结论,一个函数同时被两个装饰器时,加在下面的装饰器先装饰
1.timmer装饰器装饰原始的index,可以写成:index=timmer(index)
2.在timmer装饰器中,timmer装饰器实际上是返回inner的内存地址,所以在这里,index=inner
3.timmer装饰器装饰完成后,由auth装饰器来装饰,此时可以写成index=auth(index),
4.这里auth括号里的index已经不再是原始index函数,而是已经被timmer装饰过后的index了,所以index=auth(timmer(index))
5.又因为timmer装饰的结果等于inner函数的内存地址,所以:index=auth(inner)
至此,两个装饰器的装饰过程已经知道了,来看程序的执行过程
6.程序先执行auth装饰器,进入用户认证,请用户输入用户名和密码
7.用户输入正确的用户名和密码后,开始执行func函数,也已经上面分析的inner函数
8.timmer装饰器先定义程序的开始运行时间,然后运行func函数,也就是原生的index函数
9.index函数先睡眠2秒,然后执行print语句,再定义程序的结束时间
10.最后统计并打印程序的运行时间,至此程序运行完毕。
所以这里用户输入用户名和密码的时间不会被timmer装饰器统计在内
7.被装饰函数参数的设置与定义
先来看一段代码
import time
def timmer(func):
def inner():
start_time=time.time()
func()
end_time=time.time()
print("run time: %s " %(end_time-start_time))
return inner
@timmer
def index():
time.sleep(2)
print("welcome to index page")
@timmer
def home(name):
time.sleep(3)
print("welcome to %s home page" % name)
如上所示,home函数添加了一个参数,而index函数并没有参数
按照正常的函数的定义与调用方式,调用index函数和home函数的方式应该是下面这种形式
index()
home("python")
然后我们运行程序就会发现,程序抛出了异常
File "E:\python_learn\py_code\test.py", line 28, in <module>
home("python")
TypeError: inner() takes 0 positional arguments but 1 was given
说个异常说明inner函数不需要位置参数,但是我们给了一个位置参数
回到timmer装饰器定义的部分,可以看到,timmer装饰器的内部函数确实没有定义参数
这样一来,timmer装饰器只能用于装饰没有参数的函数了,
我们可以在timmer装饰器定义的时候为inner函数添加一个参数
import time
def timmer(func):
def inner(name):
start_time=time.time()
func(name)
end_time=time.time()
print("run time: %s " %(end_time-start_time))
return inner
@timmer
def index():
time.sleep(2)
print("welcome to index page")
@timmer
def home(name):
time.sleep(3)
print("welcome to %s home page" % name)
index()
home("python")
但是这样一来,timmer装饰器装饰index函数的时候又会抛出异常,因为index函数没有参数
File "E:\python_learn\py_code\test.py", line 27, in <module>
index()
TypeError: inner() missing 1 required positional argument: 'name'
在不知道被装饰函数的参数个数的情况下,即被装饰函数的参数可变长,且形式不固定的时候,
可以使用*args和**kwargs,把上面的代码修改
import time
def timmer(func):
def inner(*args,**kwargs):
start_time=time.time()
func(*args,**kwargs)
end_time=time.time()
print("run time: %s " %(end_time-start_time))
return inner
@timmer
def index():
time.sleep(2)
print("welcome to index page")
@timmer
def home(name):
time.sleep(3)
print("welcome to %s home page" % name)
index()
home("python")
再次运行程序,查看运行结果
welcome to index page
run time: 2.0
welcome to python home page
run time: 3.0
由上可知,在不知道被装饰函数的参数个数时,可以使用*args和**kwargs来表示任意长度任意形式的参数
8.被装饰函数的返回值
修改上面的代码,为home函数定义一个返回值,分别打印index函数和home函数的返回值
import time
def timmer(func):
def inner(*args,**kwargs):
start_time=time.time()
func(*args,**kwargs)
end_time=time.time()
print("run time: %s " %(end_time-start_time))
return inner
@timmer
def index():
time.sleep(2)
print("welcome to index page")
@timmer
def home(name):
time.sleep(3)
print("welcome to %s home page" % name)
return("home func")
index_res=index()
print(index_res)
home_res=home("python")
print(home_res)
运行程序,可以看到
welcome to index page
run time: 2.0
None
welcome to python home page
run time: 3.0
None
可以看到,home函数中定义的返回值并没有被打印出来,显示的值为None
因为这里执行的home函数不是原始定义的home函数,而是wrapper函数的执行结果
因为wrapper函数并没有定义返回值,所以执行被装饰后的home函数并没有打印出返回值
修改代码,在timmer装饰器中定义并返回被装饰函数执行的返回值
import time
def timmer(func):
def inner(*args,**kwargs):
start_time=time.time()
res=func(*args,**kwargs)
end_time=time.time()
print("run time: %s " %(end_time-start_time))
return res
return inner
@timmer
def index():
time.sleep(2)
print("welcome to index page")
@timmer
def home(name):
time.sleep(3)
print("welcome to %s home page" % name)
return("home func")
index_res=index()
print(index_res)
home_res=home("python")
print(home_res)
再次执行函数,查看执行结果
welcome to index page
run time: 2.0
None
welcome to python home page
run time: 3.0
home func
可以看来,原始home函数中定义的返回值被打印出来了
结论:
如果被装饰函数没有定义返回值,timmer装饰器装饰后的返回值为None
而如果被装饰函数定义了返回值,则timmer装饰器装饰后则返回被装饰函数的返回值
9.wraps内置方法的作用
查看一个函数的帮助文档有两种方法
func_name.__doc__
help(func_name)
先来看一个例子,定义timmer装饰器和index函数,并且都添加了帮助文档
import time
def timmer(func):
def inner(*args,**kwargs):
'wrapper inner function'
start_time=time.time()
res=func(*args,**kwargs)
end_time=time.time()
print("run time: %s " %(end_time-start_time))
return res
return inner
def index():
'index function'
time.sleep(2)
print("welcome to index page")
在index没有被timmer装饰前,来查看index的帮助文档
print(index.__doc__)
程序运行结果
index function
然后为index添加timmer装饰器,再次查看index函数的帮助文档
import time
def timmer(func):
def inner(*args,**kwargs):
'wrapper inner function'
start_time=time.time()
res=func(*args,**kwargs)
end_time=time.time()
print("run time: %s " %(end_time-start_time))
return res
return inner
@timmer
def index():
'index function'
time.sleep(2)
print("welcome to index page")
print(index.__doc__)
程序运行结果
wrapper inner function
可以看到,在为index函数添加装饰器后,index函数的帮助文档变成装饰器timmer内部函数的帮助文档了
换句话说,就是原始index函数内部的数据被装饰器timmer修改了
怎么样才能在保留原始被装饰函数的数据的前提下,为函数添加新功能呢??就是python内置的wraps装饰器
导入wraps装饰器,修改上面的代码,为timmer的内部函数添加wraps装饰器,然后再次查看被装饰函数的帮助文档
import time
from functools import wraps
def timmer(func):
@wraps(func)
def inner(*args,**kwargs):
'wrapper inner function'
start_time=time.time()
res=func(*args,**kwargs)
end_time=time.time()
print("run time: %s " %(end_time-start_time))
return res
return inner
@timmer
def index():
'index function'
time.sleep(2)
print("welcome to index page")
print(index.__doc__)
运行程序,执行结果如下
index function
可以看到,index函数即使添加了装饰器,其内部的原始数据仍然没有被装饰器修改
从上面的示例可以看出,wraps装饰器的作用就是保留被装饰对象的原始数据信息
python函数式编程之装饰器(一)的更多相关文章
- python函数式编程之装饰器(二)
以前用装饰器,都是定义好了装饰器后,使用@装饰器名的方法写入被装饰函数的正上方 在这里,定义的装饰器都是没有参数的 在定义装饰器的函数的时候,没有在括号里定义参数,这就叫做无参装饰器 既然有无参装饰器 ...
- Python函数式编程之装饰器
原则:对修改是封闭的,对扩展是开放的,方法:一般不修改函数或者类,而是扩展函数或者类 一:装饰器 允许我们将一个提供核心功能的对象和其他可以改变这个功能的对象’包裹‘在一起, 使用装饰对象的任何对象与 ...
- Python模块化编程与装饰器
Python的模块化编程 我们首先以一个例子来介绍模块化编程的应用场景,有这样一个名为requirements.py的python3文件,其中两个函数的作用是分别以不同的顺序来打印一个字符串: # r ...
- python高级编程之装饰器04
from __future__ import with_statement # -*- coding: utf-8 -*- # python:2.x __author__ = 'Administrat ...
- python函数式编程之返回函数、匿名函数、装饰器、偏函数学习
python函数式编程之返回函数 高阶函数处理可以接受函数作为参数外,还可以把函数作为结果值返回. 函数作为返回值 def laxy_sum(*args): def sum(): ax = 0; fo ...
- Python编程举例-装饰器
装饰器的通常用途是扩展已定义好的函数的功能 一个浅显的装饰器编程例子 #装饰器函数 def outer(fun): def wrapper(): #添加新的功能 print('验证') fun() r ...
- Python函数式编程(进阶2)
转载请标明出处: http://www.cnblogs.com/why168888/p/6411915.html 本文出自:[Edwin博客园] Python函数式编程(进阶2) 1. python把 ...
- Python中利用函数装饰器实现备忘功能
Python中利用函数装饰器实现备忘功能 这篇文章主要介绍了Python中利用函数装饰器实现备忘功能,同时还降到了利用装饰器来检查函数的递归.确保参数传递的正确,需要的朋友可以参考下 " ...
- C#中的 Attribute 与 Python/TypeScript 中的装饰器是同个东西吗
前言 最近成功把「前端带师」带入C#的坑(实际是前端带师开始从cocos转unity游戏开发了) 某天,「前端带师」看到这段代码后问了个问题:[这个是装饰器]? [HttpGet] public Re ...
随机推荐
- 2017ICPC/广西邀请赛1001(水)HDU6181
A Math Problem Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)To ...
- Windows系统下文件的概念及c语言对其的基本操作(丙)
- YUI 和路径相关的参数与module加载之间的关系
相关参数默认值 使用YUI, 我们可以配置一些和路径相关参数,如base.root.comboBase.cdn, combine.path.fullpath等属性的配置均会影响到YUI的module加 ...
- 用dedecms做网站时,空间服务器选择IIS还是apache???
想做一个dedecms程序的网站,不知道要选择什么样的空间,windows还是linux的?多大的空间比较适合?求高人回答. 如果是基于Linux平台的话,那不必多说自然是Apache了,因为II ...
- 那些年~~~我们的C#笔试内测题目
<深入.NET平台和C#编程>内部测试题-笔试试卷 一 选择题 1) 以下关于序列化和反序列化的描述错误的是( C). a) 序列化是将对象的状态存储到特定存储介质中的过程 b) 二进制格 ...
- [SinGuLaRiTy] 复习模板-图论
[SinGuLaRiTy-1041] Copyright (c) SinGuLaRiTy 2017. All Rights Reserved. 计算树的直径 //方法:任选一个点作为起点进行一次BFS ...
- FORM ACTION=""
FORM ACTION="" 如果什么都不写,就表示提交到当前页
- ArrayList 源码详细分析
1.概述 ArrayList 是一种变长的集合类,基于定长数组实现.ArrayList 允许空值和重复元素,当往 ArrayList 中添加的元素数量大于其底层数组容量时,其会通过扩容机制重新生成一个 ...
- Activity内切换fragment实现底部菜单切换遇到的坑
1.一般说来,app底部导航都会设计为5个菜单,可以使用textView,也可使用radioButton,这里我选择用radioButton,给radioButton直接设置selector就可以实现 ...
- tomcat三种启动不同的启动方式
Linux下tomcat服务的启动.关闭与错误跟踪,通常通过以下几种方式启动关闭tomcat服务: 切换到tomcat主目录下的bin目录 1. 启动tomcat服务 方式一:直接启动 ./start ...