python自学——装饰器
开放封闭原则和装饰器的应用
- 已经封装好的代码,可以去拓展,但是如果去修改其内部的代码,是不允许的
- 装饰器的作用:在不更改原功能函数内部代码,并且不更改调用方法的情况下,为原函数添加新的功能
- 装饰器的应用场景:
-
- 登陆验证
-
- 函数运行时间统计
-
- 执行函数之前做准备工作
-
- 执行函数之后的清理功能
装饰器的演变
初始版本
需求:一个加法函数,想增强他的功能,能够输出比哪调用过一集调用的参数信息
简单的实现,代码如下:
def add(x,y):
#增加他的输出功能
print("call add,x+y")#日志输出到控制台
return x+y
k = add(4,5)
print(k)
- 以上代码是完成了需求,但是有以下缺点:
- 打印语句的耦合性太好,他和函数紧紧的粘合在了一起
- 加法属于业务功能,而输出信息的功能属于非业务功能,不应该放在业务函数中。属于侵入式代码
改进第一步,通过高阶函数来实现
def add(x,y):
return x+y
def logger(fn):#将函数名传入另外一个函数中
print("begin")
x = fn(4,5)
print("end")
return x
print(logger(add))
===========run_result==========
begin
end
9
- 以上代码虽然做到了业务分离,但是fn函数调用传参是个问题,参数被写死了
- 如何解决传参问题呢?代码如下:
def add1(x,y):
return x+y
def add2(x,y,z):
return x+y+z
def add3(x,y,*args,z):
return x+y+z
def logger(fn,*args,**kwargs):
print("before")
ret = fn(*args,**kwargs)
print("after")
return ret
print("add1:",logger(add1,4,5),end=" ")
print("=========分割线=============")
print("add2:",logger(add2,4,5,6),end=" ")
#add2需要传三个参数,如果add1,就需要传2个,否则报错
print("=========分割线=============")
print("add3:",logger(add3,4,5,6,z=7),end=" ")
==============run_result============
before
after
add1: 9 =========分割线=============
before
after
add2: 15 =========分割线=============
before
after
add3: 16
再次优化,柯里化
- 柯里化,嵌套函数,外层函数返回内层函数的函数对象
def add1(x,y):
return x+y
def add2(x,y,z):
return x+y+z
def add3(x,y,*args,z):
return x+y+z
def logger(fn):
def logger_1(*args,**kwargs):
print("before")
ret = fn(*args,**kwargs)
print("after")
return ret
return logger_1
print(logger(add1)(4,5))
print("=========分割线=============")
print(logger(add2)(4,5,6))
print("=========分割线=============")
print(logger(add3)(4,5,6,z=7))
============run_result===============
before
after
9
=========分割线=============
before
after
15
=========分割线=============
before
after
16
再次优化,装饰器
def logger(fn):
def logger_1(*args,**kwargs):
print("before")
ret = fn(*args,**kwargs)
print("after")
return ret
return logger_1
@logger
def add1(x,y):
return x+y
@logger
def add2(x,y,z):
return x+y+z
@logger #@logger等价于 add3=logger(add3)其接收的对象是logger_1
def add3(x,y,*args,z):
return x+y+z
print(add1(4,5))
print("=========分割线=============")
print(add2(4,5,6))
print("=========分割线=============")
print(add3(4,5,6,z=7))
============run_result============
before
after
9
=========分割线=============
before
after
15
=========分割线=============
before
after
16
装饰器(无参)
-
装饰器(无参)
1.他是一个函数
2.函数作为他的形参
3.返回值也是一个函数
4.可以使用@functionname方式,简化调用 -
装饰器和高阶函数之间的关系
1.装饰器是高阶函数
2.但是装饰器是对传入函数功能的装饰(函数增强) - 以下是优化后的装饰器的代码
import datetime
import time
def logger(fn):
def logger_1(*args,**kwargs):
#befpre 功能增强
print("args={},kwargs={}".format(args,kwargs))
start = datetime.datetime.now()
ret = fn(*args,**kwargs)
#after 功能增强
duration = datetime.datetime.now()-start
print("function{}took{}s".format(fn.__name__,duration.total_seconds()))
return ret
return logger_1
@logger #等价于:add=logger(add)
def add(x,y):
print("************call add***********")
time.sleep(2)
return x+y
print(add(4,y=100))
print(add(5,1000))
=============run_result============
args=(4,),kwargs={'y': 100}
************call add***********
functionaddtook2.005193s
104
args=(5, 1000),kwargs={}
************call add***********
functionaddtook2.003411s
1005
装饰器的作用
-
功能增强,避免侵入式代码
文档字符串
- python是文档字符串Documentation Strings
- 在函数语句的第一行,切习惯是多行文本,所以多使用三引号
- 惯例是首字母大写(英文),第一行写概述,空一行,第三行写详细的描述
-
可以使用特殊属性
__doc__
来访问这个文档 - 例如以下代码:
def add(x,y):
'''This is a function of addition'''
return x+y
print("name={}\ndoc={}".format(add.__name__,add.__doc__))
print(help(add))
================run_result============
name=add
doc=This is a function of addition
Help on function add in module __main__:
add(x, y)
This is a function of addition
None
装饰器的弊端(副作用)
- 将文件字符串放到装饰器之后,调用函数时原函数的对象的属性被替换了
- 代码如下:
import datetime
def logger(fn):
def logger_1(*args,**kwargs):
'''This is a logger'''
#befpre 功能增强
print("args={},kwargs={}".format(args,kwargs))
start = datetime.datetime.now()
ret = fn(*args,**kwargs)
#after 功能增强
duration = datetime.datetime.now()-start
print("function:{},took:{}s".format(fn.__name__,duration.total_seconds()))
return ret
return logger_1
@logger #等价于:add=logger(add)
def add(x,y):
'''This is a function of addition'''
return x+y
print("name={}\ndoc={}".format(add.__name__,add.__doc__))
print(add(4,y=100))
print(add(5,1000))
=============run_result==============
name=logger_1
doc=This is a logger
args=(4,),kwargs={'y': 100}
function:add,took:3e-06s
104
args=(5, 1000),kwargs={}
function:add,took:1e-06s
1005
解决方式一
- 使用装饰器,我们的需求是查看被封装(装饰)函数的属性,如何解决?
-
答:提供一个函数(
copy_doc_name
),被封装(装饰)函数属性copy(赋值)给装饰器函数 - 调用这个函数必须在loggger函数return前
-
add(4,y=100)
在不使用装饰器的情况下,等价于:
logger(add)(4,5)
- 代码如下:
import datetime
def copy_doc_name(src,dst):
dst.__name__ = src.__name__
dst.__doc__ = src.__doc__
def logger(fn):
def logger_1(*args,**kwargs):
'''This is a logger'''
#befpre 功能增强
print("args={},kwargs={}".format(args,kwargs))
start = datetime.datetime.now()
ret = fn(*args,**kwargs)
#after 功能增强
duration = datetime.datetime.now()-start
print("function:{},took:{}s".format(fn.__name__,duration.total_seconds()))
return ret
#在这里,fn和logger_1都已经被定义,因此放在这儿最合适
#c传进来的参数,src=fn、dst=logger_1
#函数中dsc的name和doc等于src的name和doc,就是将name和doc重新赋值
#将fn(add)中的名称和doc给到logger_1,因为最终返回的是logger函数
copy_doc_name(fn,logger_1)
return logger_1
@logger #等价于:add=logger(add)
def add(x,y):
'''This is a function of addition
:x int
: y int
: return x+y
'''
return x+y
print("name={}\ndoc={}".format(add.__name__,add.__doc__))
print(add(4,y=100))
print(add(5,1000))
================run_result==============
name=add
doc=This is a function of addition
:x int
: y int
: return x+y
args=(4,),kwargs={'y': 100}
function:add,took:4e-06s
104
args=(5, 1000),kwargs={}
function:add,took:1e-06s
1005
解决方式二
- 上面的解决方式,存在个副作用,函数中的属性多种多样,不可能将所有的函数都包含在新增的函数中,如果要使用的属性在新增函数中不存在,就会报错
- 有没有比较灵活的方式呢?
- 有,在装饰函数中添加装饰器
- 通过copy_properties函数将被包装函数的属性覆盖掉原来的包装函数
- 凡事被装饰的函数都要复制这些属性,这个函数很通用,python自带的函数名称为:不需要自己写
- 可以将复制属性的函数构建成装饰器函数,带参装饰器
- 代码实现逻辑如下:
def copy_doc_name(src):
def doc_name(dst):
dst.__name__ = src.__name__
dst.__doc__ = src.__doc__
return dst
return doc_name
def logger(fn):
@copy_doc_name(fn)
#这里是将doc_name函数执行后的结果重新赋值给logger_1,等价式子为logger_1 = doc_name(logger_1)
#copy_doc_name函数为嵌套函数,因此doc_name继承了fn函数,最终实现了两个函数之间属性的直接copy(赋值)
def logger_1(*args,**kwargs):
'''This is a logger'''
#befpre 功能增强
print("args={},kwargs={}".format(args,kwargs))
start = datetime.datetime.now()
ret = fn(*args,**kwargs)
#after 功能增强
duration = datetime.datetime.now()-start
print("function:{},took:{}s".format(fn.__name__,duration.total_seconds()))
return ret
return logger_1
@logger
def add(x,y):
'''This is a function of addition
:x int
: y int
: return x+y
'''
return x+y
print("name={}\ndoc={}".format(add.__name__,add.__doc__))
print(add(4,y=100))
print(add(5,1000))
===========run_result===========
name=add
doc=This is a function of addition
:x int
: y int
: return x+y
args=(4,),kwargs={'y': 100}
function:add,took:3e-06s
104
args=(5, 1000),kwargs={}
function:add,took:1e-06s
1005
【代码解析】
-
执行
@copy_doc_name(fn)
分为两步,第一步:执行
copy_doc_name(fn)
这个函数,将返回值和@结合,并的呆返回值为
doc_name
的函数名;第二步,将@于doc_name结合,即
@doc_name
,因为他放在
logger_1
上面,因此他省略了参数:
logger_1
,也就是说:
@doc_name
等价于
logger_1 = doc_name(logger_1)
-
因为为嵌套函数,外层函数传入的fn,内层函数
doc_name
还可以使用,因此可以完成函数属性的替换
装饰器(有参)
- 带参数的装饰器:
- 他是一个函数
- 函数作为他的形参
- 返回值是一个不带参数的装饰器
- 使用@functionname(参数列表)的方式调用
-
可以看作在装饰器外层又加了一层函数
-
装饰器无参的情况下,因为他放在add函数上面,
@logger
等价于
add = logger(add)
,省略了add这个参数,其直接调用的是外层函数,获取外层函数的结果 -
装饰器有参数的情况下,使用
@copy_doc_name(fn)
,等价于
@doc_name
,又等价于
logger_1 = doc_name(logger_1)
,调用的是内层函数,返回的是内层函数执行的结果 -
注意上面的等价式子,是
直接将函数的return结果赋值给同名称的函数
,因此
装饰器返回的是的信息都是函数对象,即函数名称
,要不然被更新的函数无法调用,就会报错 -
如下面代码所示,如果装饰器中需要传多个参数,可以写成以下的代码,
三层嵌套函数的情况下,最外层的装饰器可以传多个参数
-
带参数的装饰器,最多三层
代码示例
需求:获取函数的执行时长,对时长超过阈值的函数记录一下
import datetime
def copy_doc_name(src):
def doc_name(dst):
dst.__name__ = src.__name__
dst.__doc__ = src.__doc__
return dst
return doc_name
def logger(t1,t2):
def logger_1(fn):
@copy_doc_name(fn)
def logger_2(*args,**kwargs):
'''This is a logger'''
#befpre 功能增强
start = datetime.datetime.now()
time.sleep(4)
ret = fn(*args,**kwargs)
#after 功能增强
duration = (datetime.datetime.now()-start).total_seconds()
if t2 > duration >t1::#只有执行时间大于3s,才会打印下面的语句
print("function:{},took:{}s".format(fn.__name__,duration))
return ret
return logger_2
return logger_1
@logger(3,8) #等价于:add=logger(3)(add) ====>logger_1(add)
def add(x,y):
'''This is a function of addition'''
time.sleep(2)
return x+y
print("name={}\ndoc={}".format(add.__name__,add.__doc__))
print(add(4,y=100))
print(add(5,1000))
==============run_result=============
name=add
doc=This is a function of addition
function:add,took:6.008052s
104
function:add,took:6.003909s
1005
再优化,将记录的功能提取
- 将记录的功能提取出来,这样就可以通过外部提供的函数来灵活的控制输出
- 代码如下:
import datetime
def copy_doc_name(src):
def doc_name(dst):
dst.__name__ = src.__name__
dst.__doc__ = src.__doc__
return dst
return doc_name
def logger(t1,t2,func= lambda name,duration:print("function:{},took:{}s".format(name,duration))):
def logger_1(fn):
@copy_doc_name(fn)
def logger_2(*args,**kwargs):
'''This is a logger'''
#befpre 功能增强
start = datetime.datetime.now()
time.sleep(4)
ret = fn(*args,**kwargs)
#after 功能增强
duration = (datetime.datetime.now()-start).total_seconds()
if t2 > duration >t1:
func(fn.__name__,duration)#将打印提取出来放置在函数参数中来控制
return ret
return logger_2
return logger_1
@logger(3,8) #等价于:add=logger(3)(add) ====>logger_1(add)
def add(x,y):
'''This is a function of addition'''
time.sleep(2)
return x+y
print("name={}\ndoc={}".format(add.__name__,add.__doc__))
print(add(4,y=100))
print(add(5,1000))
==============run_result===========
name=add
doc=This is a function of addition
function:add,took:6.00962s
104
function:add,took:6.009801s
1005
functors 模块的简单讲解
update_wrapper函数
- 这是一个普通函数
-
作用类似上面代码中的
copy_doc_name
函数的功能 - 语法如下:
WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__','__annotations__')
WRAPPER_UPDATES = ('__dict__',)
update_wrapper(wrapper,
wrapped,
assigned = WRAPPER_ASSIGNMENTS,
updated = WRAPPER_UPDATES)
- wrapper指的是包装函数(装饰器函数,可以理解为上面代码中的logger函数),wrapped指的是被包装函数(被装饰的函数,可以理解为上面代码的add函数)
-
assigned后跟一个元组,用于直接指定的属性从包装函数到被包装函数,
WRAPPER_ASSIGNMENTS
是要被覆盖的属性,默认指定的属性有:
__name__
、
__doc__
、
__module__
、
__qualname__
、
__annotations__
;其对应的含义是函数的名称,文档,模块名,限定名,参数注解 -
updated是一个元组,
WRAPPER_UPDATES
中是要被更新的属性,
__dict__
的属性是字典,这里面是使用key-value来保存的属性,可以更新但是不能覆盖(key相同的情况下直接更新里面的值,key不相同的情况下,是在字典中新增属性) -
增加了一个
__wrapped__
属性,保留被装饰函数的原函数(即没有被logger装饰前的add函数) - 代码示例:
import functools
import datetime
import time
def logger(t1,t2,func= lambda name,duration:print("function:{},took:{}s".format(name,duration))):
def logger_1(fn):
def logger_2(*args,**kwargs):
'''This is a logger'''
#befpre 功能增强
start = datetime.datetime.now()
time.sleep(4)
ret = fn(*args,**kwargs)
#after 功能增强
duration = (datetime.datetime.now()-start).total_seconds()
if t2 > duration >t1:
func(fn.__name__,duration)#将打印提取出来放置在函数参数中来控制
return ret
functools.update_wrapper(wrapper=logger_2,wrapped=fn)#functools模块的使用
return logger_2
return logger_1
@logger(3,8) #等价于:add=logger(3)(add) ====>logger_1(add)
def add(x,y):
'''This is a function of addition
:x int
:y int
return x+y'''
time.sleep(2)
return x+y
print("name={}\ndoc={}".format(add.__name__,add.__doc__))
print("wrapped:",add.__wrapped__)#add
print(add(4,y=100))
print(add(5,1000))
=============run_result==========
name=add
doc=This is a function of addition
:x int
:y int
return x+y
wrapped: <function add at 0x102a74700>
function:add,took:6.00895s
104
function:add,took:6.004358s
1005
wraps
- 这是一个带参数的装饰器函数,
- 使用方法如下:
import functools
import datetime
import time
def logger(t1,t2,func= lambda name,duration:print("function:{},took:{}s".format(name,duration))):
def logger_1(fn):
@functools.wraps(fn)#functools带参数的使用方式
def logger_2(*args,**kwargs):
'''This is a logger'''
#befpre 功能增强
start = datetime.datetime.now()
time.sleep(4)
ret = fn(*args,**kwargs)
#after 功能增强
duration = (datetime.datetime.now()-start).total_seconds()
if t2 > duration >t1:
func(fn.__name__,duration)#将打印提取出来放置在函数参数中来控制
return ret
return logger_2
return logger_1
@logger(3,8) #等价于:add=logger(3)(add) ====>logger_1(add)
def add(x,y):
'''This is a function of addition
:x int
:y int
return x+y'''
time.sleep(2)
return x+y
print("name={}\ndoc={}".format(add.__name__,add.__doc__))
print("wrapped:",add.__wrapped__)
print(add(4,y=100))
print(add(5,1000))
============run_result==========
name=add
doc=This is a function of addition
:x int
:y int
return x+y
wrapped: <function add at 0x1006b4700>
function:add,took:6.00993s
104
function:add,took:6.008831s
1005
装饰器装饰类
- 下方代码解析:
-
下 面代码中的
Myclass = logger(Myclass)
- 其返回值是logger_1,因此Myclass(4,5) = logger_1(4,5)
-
然后执行装饰器中的代码:fn(
args,**kwargs),其中4,5被
arg接收 - fn是传入的参数class,因此fn(*args,**kwargs)其实就是Myclass(4,5)
- 4,5 作为初始化函数赋值给了a和b,然后调用类中的add方法
def logger(fn):
def logger_1(*args,**kwargs):
print("before")
ret = fn(*args,**kwargs)
print("after")
return ret
return logger_1
@logger # 等价于:logger(Myclass)
class Myclass:
def __init__(self,a,b):
self.a = a
self.b = b
def add(self):
return self.a+self.b
a = Myclass(4,5).add()
print(a)
*********run_result**********
before
after
9
- 【注意】
- 装饰器在装饰类的时候,装饰器内层嵌套的return必须写
- 如果是装饰函数,return并不一定要写
多个装饰器装饰一个函数
- 从下往上装饰
- 从上往下执行
users = {"user":"python12","pwd":"python","token":False}
def login_in(fn):
'''
登陆验证的装饰器
:return:
'''
def ado(*args, **kwargs):
while not users.get("token"):
print("----------登陆页面---------")
user_name = input("账号:")
pwd = input("密码:")
# 登陆验证
if users.get("user") == user_name and\
users.get("pwd")==pwd:
print("登陆成功")
users["token"] = True
fn(*args, *kwargs)
else:
fn(*args, *kwargs)
return ado
import time
def get_time(fn):
def wapper(*args,**kwargs):
print("AAA")
start_t = time.time()
fn(*args,**kwargs)
time.sleep(3)
end_t = time.time()
return end_t - start_t
return wapper
@get_time
@login_in # 多个装饰器,执行的时候从上往下调用,装饰的时候从下往上装饰
def add(x,y):
print("num:",x+y)
a = add(4, 5)
print(a)
********************run_result***********
----------登陆页面---------
账号:python12
密码:python
登陆成功
num: 9
10.232401132583618
版权声明:本文为weixin_43754879原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。