文章目录
这里是一段防爬虫文本,请读者忽略。
本文原创首发于CSDN,作者IDYS
博客首页:https://blog.csdn.net/weixin_41633902/
本文链接:https://blog.csdn.net/weixin_41633902/article/details/107952932
未经授权,禁止转载!恶意转载,后果自负!尊重原创,远离剽窃!
写在开头的话
- 请记住:实践是掌握知识的最快方法
- 如果你只是怀着看看的态度去快速浏览文章,而不去认认真真的把文章里面讲的任何一个知识点去实践一遍,那么你永远也掌握不了它
- 生命不息,折腾不止!
Python 装饰器
00. 前述
0.1 需求
- 一个加法函数,想增强它的功能,能够输出被调用过以及调用的参数信息
def add(x, y):
return x + y
- 增加信息输出功能
def add(x, y):
print("call add, x + y") # 日志输出到控制台
return x + y
上面的加法函数是完成了需求,但是有以下的缺点:
- 打印语句的耦合太高
- 加法函数属于业务功能,而输出信息的功能,属于非业务功能代码,不该放在业务函数加法中
01. 实现业务分离
-
做到了业务功能分离,但是
fn
函数调用传参是个问题
def add(x,y):
return x + y
def logger(fn):
print('begin') # 增强的输出
x = fn(4,5)
print('end') # 增强的功能
return x
print(logger(add))
- 解决了传参的问题,进一步改变
def add(x,y):
return x + y
def logger(fn,*args,**kwargs):
print('begin')
x = fn(*args,**kwargs)
print('end')
return x
print(logger(add,5,y=60))
- 柯里化
def add(x,y):
return x + y
def logger(fn):
def wrapper(*args,**kwargs):
print('begin')
x = fn(*args,**kwargs)
print('end')
return x
return wrapper
print(logger(add)(5,y=50))
- 装饰器语法糖
def logger(fn):
def wrapper(*args,**kwargs):
print('begin')
x = fn(*args,**kwargs)
print('end')
return x
return wrapper
@logger # 等价于add = logger(add)
def add(x,y):
return x + y
print(add(45,40))
-
@logger
是什么?这就是装饰器语法
- 演示
def logger(fn):
def _logger(*args, **kwargs):
print("start")
ret = fn(*args, **kwargs)
print("end")
return ret
return _logger
@logger # fn = logger(fn)
def fn(*args, **kwargs):
num_sum = 0
for i in args:
num_sum += i
for k, v in kwargs.items():
num_sum += v
return num_sum
if __name__ == "__main__":
print(fn(100, 100, 200, a=234, b=56))
fn(190, 200, 300)
- 运行结果
start
end
690
start
end
02. 装饰器特点
-
装饰器(无参)
- 它是一个函数
- 函数作为它的形参
- 返回值也是一个函数
-
可以使用
@functionname
方式,简化调用
-
装饰器和高阶函数
- 装饰器是高阶函数,但装饰器是对传入函数的功能的装饰(功能增强)
- 演示
import datetime
import time
def logger(fn):
def _logger(*args, **kwargs):
print("args={},kwargs={}".format(args, kwargs))
start_time = datetime.datetime.now()
ret = fn(*args, **kwargs)
total_seconds = datetime.datetime.now() - start_time
print("{} spend{}s".format(fn.__name__, total_seconds.total_seconds()))
return ret
return _logger
@logger # fn = logger(fn)
def fn(*args, **kwargs):
time.sleep(2)
num_sum = 0
for i in args:
num_sum += i
for k, v in kwargs.items():
num_sum += v
return num_sum
if __name__ == "__main__":
print(fn(10, 100, 20, x=30, y=50))
- 运行结果
args=(10, 100, 20),kwargs={'x': 30, 'y': 50}
fn spend2.000723s
210
03. 文档字符串
-
Python
的文档-
Python是文档字符串
Documentation Strings
-
Python是文档字符串
- 在函数语句块的第一行,且习惯是多行的文本,所以多使用三引号
- 惯例是首字母大写,第一行写概述,空一行,第三行写详细描述
-
可以使用特殊属性
__doc__
访问这个文档
def add(x,y):
"""This is a function of addition"""
a = x+y
return x + y
print("name={}\ndoc={}".format(add.__name__, add.__doc__)) print(help(add))
- 副作用
def logger(fn):
def _logger(*args, **kwargs):
"""
This is logger function
:param args:
:param kwargs:
:return:
"""
print("before")
ret = fn(*args, **kwargs)
print("after")
return ret
return _logger
@logger # add = logger(add)
def add(x, y):
"""
This is a added function
:param x:
:param y:
:return: int
实现函数的参数相加
"""
a = x+y
return x + y
if __name__ == "__main__":
print("name={}\ndoc={}".format(add.__name__, add.__doc__))
print(help(add))
print()
print("*******************")
print(add(1, 23))
- 运行结果
name=_logger
doc=
This is logger function
:param args:
:param kwargs:
:return:
Help on function _logger in module __main__:
_logger(*args, **kwargs)
This is logger function
:param args:
:param kwargs:
:return:
None
可以发现原函数的属性全部被替换为装饰器函数的属性了。这使得如果我们想查看原函数的帮助信息和文档信息,最后却看到的是装饰器的帮助信息和文档信息。这样使用装饰器就背道而驰了
- 改造一下,将装饰器里面的函数的属性替换为传入的函数的属性
def copy_properties(src, dest): # 复制函数属性
dest.__name__ = src.__name__
dest.__doc__ = src.__doc__
dest.__qualname__ = src.__qualname__
def logger(fn):
def warpper(*args, **kwargs):
"""
This is warpper function
:param args:
:param kwargs:
:return:
"""
print("before")
ret = fn(*args, **kwargs)
print("after")
return ret
copy_properties(fn, warpper) # 调用函数属性复制函数
return warpper
@logger # add = logger(add) 装饰器
def add(x, y):
"""
This is a added function
:param x:
:param y:
:return: int
实现函数的参数相加
"""
a = x+y
return x + y
if __name__ == "__main__":
print("name={}\ndoc={}".format(add.__name__, add.__doc__))
print(help(add))
- 运行结果
name=add
doc=
This is a added function
:param x:
:param y:
:return: int
实现函数的参数相加
Help on function add in module __main__:
add(*args, **kwargs)
This is a added function
:param x:
:param y:
:return: int
实现函数的参数相加
None
这种方式成功实现了将原函数的属性复制给了装饰器内部函数。实现了添加了装饰器的功能但是没有改变原来函数的属性。不过,这种方式还不够
-
通过
copy_properties
函数将被包装函数的属性覆盖掉包装函数 -
凡是被装饰的函数都需要复制这些属性,这个函数很通用
-
可以将复制属性的函数构建成装饰器函数,带参装饰器
04. 带参装饰器
- 改装成带参装饰器,实现函数属性的复制
def copy_properties(src):
def _copy_properties(dest):
dest.__name__ = src.__name__
dest.__doc__ = src.__doc__
dest.__qualname__ = src.__qualname__
return dest
return _copy_properties
def logger(fn):
@copy_properties(fn) # 通过带参装饰器实现函数复制 warpper = copy_properties(fn)(warpper)
def warpper(*args, **kwargs):
"""
This is warpper function
:param args:
:param kwargs:
:return:
"""
print("before")
ret = fn(*args, **kwargs)
print("after")
return ret
return warpper
@logger # add = logger(add)
def add(x, y):
"""
This is a added function
:param x:
:param y:
:return: int
实现函数的参数相加
"""
a = x+y
return x + y
if __name__ == "__main__":
print("name={}\ndoc={}".format(add.__name__, add.__doc__))
print(help(add))
- 运行结果
name=add
doc=
This is a added function
:param x:
:param y:
:return: int
实现函数的参数相加
Help on function add in module __main__:
add(*args, **kwargs)
This is a added function
:param x:
:param y:
:return: int
实现函数的参数相加
None
上面的程序中,通过
@copy_properties(fn)
,带参装饰器。实现了将原函数的属性赋值到目标函数属性上面去
4.1 需求
- 获取函数的执行时长,对时长超过阈值的函数记录一下
思路:因为要获取函数执行时长,对时长超过阈值的函数记录一下。
- 不改变原函数,不对原函数做任何修改。
- 因为要对时长超过阈值的函数做记录,那么需要对阈值进行传参
- 因为要对函数功能增强,但是又不修改原函数代码。那么则采用装饰器
- 因为要增加一个阈值参数,那么则使用带参装饰器
- 因为要记录时长那么则导入
datetime
模块
- 代码演示
import datetime
import time
def copy_properties(src):
def _copy_properties(dest):
dest.__name__ = src.__name__
dest.__doc__ = src.__doc__
dest.__qualname__ = src.__qualname__
return dest
return _copy_properties
def logger(timing):
def _logger(fn):
@copy_properties(fn) # 通过带参装饰器实现函数复制 warpper = copy_properties(fn)(warpper)
def warpper(*args, **kwargs):
"""
This is warpper function
:param args:
:param kwargs:
:return:
"""
print("before")
start_time = datetime.datetime.now()
ret = fn(*args, **kwargs)
total_time = (datetime.datetime.now() - start_time).total_seconds()
print("fast") if total_time < timing else print("slow")
print("after")
return ret
return warpper
return _logger
@logger(5) # add = logger(5)(add)
def add(x, y):
"""
This is a added function
:param x:
:param y:
:return: int
实现函数的参数相加
"""
time.sleep(6)
a = x+y
return x + y
if __name__ == "__main__":
print("name={}\ndoc={}".format(add.__name__, add.__doc__))
print(help(add))
add(3, 8)
- 运行结果
name=add
doc=
This is a added function
:param x:
:param y:
:return: int
实现函数的参数相加
Help on function add in module __main__:
add(*args, **kwargs)
This is a added function
:param x:
:param y:
:return: int
实现函数的参数相加
None
before
slow
after
下面可以推出一个结论。每次要增加一个功能时,那么我们只需要再往外面套一个函数,继续柯里化。这样就可以保证每次虽然增加了函数的功能,但是没有对原函数进行修改。
4.2 带参装饰器的特性
-
带参装饰器
- 它是一个函数
- 函数作为它的形参
- 返回值是一个不带参的装饰器函数
-
使用
@functionname
(参数列表)方式调用 - 可以看做在装饰器外层又加了一层函数
4.3 代码继续优化
-
将记录的功能提取出来,这样就可以通过外部提供的函数来灵活的控制输出
-
将记录的功能抽取为一个装饰器里面的一个参数,这样就可以随意改变里面的功能
- 代码演示
import datetime
import time
def copy_properties(src):
def _copy_properties(dest):
dest.__name__ = src.__name__
dest.__doc__ = src.__doc__
dest.__qualname__ = src.__qualname__
return dest
return _copy_properties
def logger(timing,compare=lambda name, timing, program_time: print("{} have take {} seconds".format(name, timing)) if timing > program_time else False ):
def _logger(fn):
@copy_properties(fn) # 通过带参装饰器实现函数复制 warpper = copy_properties(fn)(warpper)
def warpper(*args, **kwargs):
"""
This is warpper function
:param args:
:param kwargs:
:return:
"""
print("before")
start_time = datetime.datetime.now()
ret = fn(*args, **kwargs)
total_time = (datetime.datetime.now() - start_time).total_seconds()
compare(fn.__name__, total_time, timing)
print("after")
return ret
return warpper
return _logger
@logger(5)
def add(x, y):
"""
This is a added function
:param x:
:param y:
:return: int
实现函数的参数相加
"""
time.sleep(4)
a = x+y
return x + y
if __name__ == "__main__":
print("name={}\ndoc={}".format(add.__name__, add.__doc__))
print(help(add))
add(3, 8)
- 运行结果
name=add
doc=
This is a added function
:param x:
:param y:
:return: int
实现函数的参数相加
Help on function add in module __main__:
add(*args, **kwargs)
This is a added function
:param x:
:param y:
:return: int
实现函数的参数相加
None
before
after
05. functools 模块
5.1 functools 模块的普通函数实现属性复制
-
functools.update_wrapper(wrapper, wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)
-
类似copy_properties功能
-
wrapper
包装函数、被更新者,
wrapped
被包装函数、数据源 -
元组
WRAPPER_ASSIGNMENTS
中是要被覆盖的属性
__module__
,
__name__
,
__qualname__
,
__doc__
,
__annotations__
模块名、名称、限定名、文档、参数注解 -
元组
WRAPPER_UPDATES
中是要被更新的属性,
__dict__
属性字典 -
增加一个
__wrapped__
属性,保留着
wrapped
函数
-
使用
functools
的
update_wrapper
实现装饰函数到被装饰函数的属性复制
- 代码演示
import functools
def logger(fn):
def warpper(*args, **kwargs):
"""
This is warpper function
:param args:
:param kwargs:
:return:
"""
print("before")
ret = fn(*args, **kwargs)
print("after")
return ret
functools.update_wrapper(warpper, fn)
print("{:^}\t{:^}".format(id(warpper), id(fn)))
return warpper
@logger
def add(x, y):
"""
This is a added function
:param x:
:param y:
:return: int
实现函数的参数相加
"""
a = x+y
return x + y
if __name__ == "__main__":
print("name={}\ndoc={}".format(add.__name__, add.__doc__))
print(help(add))
print(id(add.__wrapped__))
- 运行结果
1696978872496 1696978482512
name=add
doc=
This is a added function
:param x:
:param y:
:return: int
实现函数的参数相加
Help on function add in module __main__:
add(x, y)
This is a added function
:param x:
:param y:
:return: int
实现函数的参数相加
None
1696978482512
5.2 functools 的装饰器方法实现属性复制
-
@functools.wraps(wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)
-
类似
copy_properties
功能 -
wrapped
被包装函数 -
元组
WRAPPER_ASSIGNMENTS
中是要被覆盖的属性
__module__
,
__name__
,
__qualname__
,
__doc__
,
__annotations_
, 模块名、名称、限定名、文档、参数注解 -
元组
WRAPPER_UPDATES
中是要被更新的属性,
__dict__
属性字典 -
增加一个
__wrapped__
属性,保留着
wrapped
函数
- 代码演示
import functools
def logger(fn):
@functools.wraps(fn)
def warpper(*args, **kwargs):
"""
This is warpper function
:param args:
:param kwargs:
:return:
"""
print("before")
ret = fn(*args, **kwargs)
print("after")
return ret
print("{:^}\t{:^}".format(id(warpper), id(fn)))
return warpper
@logger
def add(x, y):
"""
This is a added function
:param x:
:param y:
:return: int
实现函数的参数相加
"""
a = x+y
return x + y
if __name__ == "__main__":
print("name={}\ndoc={}".format(add.__name__, add.__doc__))
print(help(add))
print(id(add.__wrapped__))
- 运行结果
2721360226480 2721359836496
name=add
doc=
This is a added function
:param x:
:param y:
:return: int
实现函数的参数相加
Help on function add in module __main__:
add(x, y)
This is a added function
:param x:
:param y:
:return: int
实现函数的参数相加
None
2721359836496
写在最后的话:
- 无论每个知识点的难易程度如何,我都会尽力将它描绘得足够细致
-
欢迎关注我的CSDN博客,
IDYS’BLOG
-
持续更新内容
:
linux基础 | 数据通信(路由交换,WLAN) | Python基础 | 云计算
- 如果你有什么疑问,或者是难题。欢迎评论或者私信我。你若留言,我必回复!
- 虽然我现在还很渺小,但我会做好每一篇内容。谢谢关注!