在程序中存在高度重复的代码时,我们就需要考虑使用元编程了,主要技术有装饰器、类装饰器和元类。
一个装饰器就是一个函数,它将一个函数作为参数并返回一个新的函数。
下面的函数中,装饰器的目的是计算任意一个函数的运行时间。这是一个很简单的装饰器,但是其中有很多细节值得注意。
wrap的意思是包装、包裹,@wrap是python的一个装饰器工具。
import time
from functools import wraps
def time_use(func): # 接收函数 func()作为参数
@wraps(func) # @wraps的作用是使装饰器保留原始函数的元数据,不改变其结构和属性,如 name、doc等
def wrapper(*args, **kwargs): # *args, **kwargs不能少,确保 wrapper()可以接收任意参数的函数
start = time.time()
result = func(*args, **kwargs) # 为了保证 wrapper()代码不变,这里的 func()也需要保证接收任意参数
end = time.time()
print(f"function name: {func.__name__}(), time use: {end - start:0.5f}s")
return result
return wrapper # 装饰器返回的是一个函数
@time_use # @装饰符的作用是将 count()作为 time_use()的参数
def count(n):
while n > 0:
n -= 1
if __name__ == '__main__':
count(100000) # 注意这里执行的是原始函数 count(),而不是装饰器 @time_use(),但实现的是装饰器的功能。
count(10000000)
# output:
function name: count(), time use: 0.00997s
function name: count(), time use: 0.77194s
其中*args和**kwargs是可变长参数,*args用于让函数接收任意数量的位置参数,这些位置参数构成一个tuple;**kwargs用于让函数接收任意数量的关键字参数,这些关键字参数构成一个dict。
可变长参数适用于为函数添加装饰器、参数数目不确定、实现函数多态、继承情况下子类需要调用父类的某些方法等情形。
关于可变长参数的更多介绍可见《Python 进阶编程》7.2节。
func. __ name __ 的作用是返回函数名。
func. __ doc __ 的作用是返回文件开头注释的内容,即文档字符串DocString。
func. __ annotations __ 的作用是返回一个key为函数形参、value为形参的数据类型注解的字典。
如下面的例子中,def count(m, n: float) -> None 函数中,参数 n和返回值有数据类型注解,所以 __ annotations __ 只返回参数 n和返回值的dict,并不返回参数m 的dict。因为m没有value值,无法组成字典。
这些都是在是在使用了 @wrap的情况下实现的,如果没有用 @wrap,原始函数会丢失所有有用的元数据信息。
代码示例:
import time
from functools import wraps
def time_use(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"function name: {func.__name__}, time use: {end - start:0.5f}s")
return result
return wrapper
@time_use
def count(m, n: float) -> None:
"""
:param m: any data type
:param n: float, 注意 pyhton中没有 double类型
:return: None
"""
while m + n > 0:
n -= 1
if __name__ == '__main__':
count(10, 100000)
print(f'function name: {count.__name__}')
print(f'function doc: {count.__doc__}')
print(f'function annotations: {count.__annotations__}')
# output:
function name: count, time use: 0.01896s
function name: count
function doc:
: param m : any data type
: param n: float,注意pyhton中没有double类型
: return: None
function annotations: {'n': <class 'float'>, 'return': None}
关于python函数的更多属性可见博客:详解Python中函数和模块的特殊属性
比如下面的例子,想要在 time_use() 中增加一个线程休眠的参数,就需要在 time_use() 中实现一个内部装饰器,整体变成三个函数三个 return的装饰器。但是其基本思想很简单:最外层的函数接收参数并将参数作用在内部的装饰器函数中。
这时就不能写成 @time_use了,参数也要带上,写成 @time_use(3)
import time
from functools import wraps
def time_use(sleep_time): # 带参数的装饰器
def decotrate(func): # 增加一层函数,写成内部装饰器
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
time.sleep(sleep_time) # 线程休眠
end = time.time()
print(f"function name: {func.__name__}, time use: {end - start:0.5f}s")
return result
return wrapper
return decotrate
@time_use(3) # 装饰器也要带参数,等价于 time_use(3)(count)
def count(m, n):
while m + n > 0:
n -= 1
if __name__ == '__main__':
count(10, 100000)
# output:
function name: count, time use: 3.02121s