python装饰器理解以及常用的装饰器介绍
这里这个整理一下python中的装饰器的用法,以及在看代码时经常会看到的一些常用装饰器.
一. 装饰器
必须记住的几点:
- 装饰器能将被装饰的函数替换为其他的函数(下边例子的第三行的输出结果显示)
- 装饰器在加载模块的时候立即执行 ** (下边例子的前两行的输出**结果)
- 装饰器一般在一个模块中定义,在另外一个模块中使用 (下方例子的简单示例所示的组织方式)
- 装饰器可以带有参数,这就是参数化装饰器,也就是装饰器的工厂函数 (第二个例子所示的参数化装饰器)
一个简单的装饰器的例子:
# 装饰器的定义文件clock_register.py, 装饰器的作用为打印函数运行时间,传入的参数以及调用的结果返回.
import time
def clock(func):
print('Decroate the function:', func.__name__)
def clocked(*args):
t0 = time.perf_counter() # 作为初始时间,构成下文中的elapsed(函数运行的消耗时间.)
result = func(*args)# 被装饰函数的执行
elapsed = time.perf_counter() - t0
name = func.__name__ # 装饰器所修饰的函数的名称
arg_str = ', '.join(repr(arg) for arg in args) # 传入参数的字符格式
# 格式化输出,分别打印函数的运行时间, 函数名称, 传入的参数, 函数计算结果
print('[%0.8fs] %s(%s) -> %r' % (elapsed, name, arg_str, result))
return result
return clocked
###### clock装饰器的调用模块
from clock_register import clock
import time
@clock
def snooze(seconds):
time.sleep(seconds)
@clock
def factorial(n):
if n < 2:
return 1
return n * factorial(n-1)
if __name__ == '__main__':
print(snooze)
print('*' * 40, 'calling snooze(.123)')
snooze(.123)
print('*' * 40, 'calling factorial(6)')
print('6!=', factorial(6))
输出结果:
前两行的输出结果说明: 装饰器在加载模块时立即执行
第三行的输出结果显示: 装饰器能将被装饰的函数替换为其他的函数, 此时的snooze函数为clocked函数,传入的args=0.123
为clock的参数.
Decroate the function: snooze
Decroate the function: factorial
<function clock.<locals>.clocked at 0x7f770f855b00>
**************************************** calling snooze(.123)
[0.12307592s] snooze(0.123) -> None
**************************************** calling factorial(6)
[0.00000098s] factorial(1) -> 1
[0.00002348s] factorial(2) -> 2
[0.00003814s] factorial(3) -> 6
[0.00005152s] factorial(4) -> 24
[0.00006555s] factorial(5) -> 120
[0.00008150s] factorial(6) -> 720
6!= 720
参数化的装饰器的例子
一个简单的例子
### 参数化的装饰器
DEFAULT_FMT = '[{elapsed}s] {name}({args}) -> {result}'
def clock_args(fmt = DEFAULT_FMT):
def decroate(func):
print('Decroate the function:', func.__name__)
def clocked(*args):
t0 = time.perf_counter() # 作为初始时间,构成下文中的elapsed(函数运行的消耗时间.)
result = func(*args)# 被装饰函数的执行
elapsed = time.perf_counter() - t0
name = func.__name__ # 装饰器所修饰的函数的名称
arg_str = ', '.join(repr(arg) for arg in args) # 传入参数的字符格式
# 格式化输出,按照参数化,打印指定的输出格式, 这里采用**locals()获取局部参数
print(fmt.format(**locals()))
return result
return clocked
return decroate
####### 参数化装饰器的调用
from clock_register import clock, DEFAULT_FMT, clock_args
import time
@clock_args()
def snooze(seconds):
time.sleep(seconds)
@clock_args(fmt='{name}: {elapsed}s')
def snooze1(seconds):
time.sleep(seconds)
if __name__ == '__main__':
print(snooze1)
print('*' * 40, 'calling snooze(.123)')
snooze(.123)
print('*' * 40, 'calling snooze(.123)')
snooze1(.123)
最终的输出结果:
Decroate the function: snooze
Decroate the function: snooze1
<function clock_args.<locals>.decroate.<locals>.clocked at 0x7f770f84f0e0>
**************************************** calling snooze(.123)
[0.1231747239944525s] snooze((0.123,)) -> None
(base) walle@walle-All-Series:~/123$ python clock_test.py
Decroate the function: snooze
Decroate the function: snooze1
**************************************** calling snooze(.123)
[0.12314573698677123s] snooze((0.123,)) -> None
**************************************** calling snooze(.123)
snooze1: 0.12315590702928603s
二. 常用的装饰器
@property
参考这篇文章:@property装饰器的简单理解
@classmethod与@staticmethod
这两个装饰器在很多的代码中经常会看到,@classmethod叫类方法, @staticmethod叫静态方法,下边以一个简单的例子分析这两个迭代器的用法以及一些区别.
简单的例子:
class A:
def __init__(self):
pass
# 实例方法, 约定俗成第一个参数为self, 与实例化对象绑定
def m1(self, n):
print("self: ", self)
# 类方法, 约定俗成第一个参数为 cls, 与类绑定
@classmethod
def m2(cls, n):
print('cls: ', cls)
# 静态方法(其实就是一个普通的函数,只是位于类中,与类和实例均没有绑定关系)
@staticmethod
def m3(n):
print('this is a static method.')
a = A()
a.m1(1)
a.m2(1)
A.m2(1)
A.m3(1)
a.m3(1)
m3(1)
输出结果为:
self: <__main__.A object at 0x7f9a9eb50e90>
cls: <class '__main__.A'>
cls: <class '__main__.A'>
this is a static method.
this is a static method.
Traceback (most recent call last):
File "classmethod_staticmethod.py", line 24, in <module>
m3(1)
NameError: name 'm3' is not defined
依据以上结果可以看出:
a = A() # 实例化一个实例对象
a.m1(1) # m1是一个实例方法,通过实例a调用
a.m2(1) # m2是一个类方法,通过实例a找到类A,然后通过A调用m2
A.m2(1) # 利用类直接调用类方法.
A.m3(1) # 静态方法,既可以直接使用类A调用,也可以使用实例a调用
a.m3(1)
m3(1) # 不可直接调用
@functools.lru_cache
学操作系统的时候lru这个东西经常出现,在leetcode上也有实现lru的这个题目,这个装饰器可以实现这种功能.
例如一个求斐波那契数列的函数:
@clock
def fibonacci(n):
if n < 2:
return n
return fibonacci(n-2) + fibonacci(n-1)
if __name__ == '__main__':
print(fibonacci(6))
结果为:
[0.00000133s] fibonacci(0) -> 0
[0.00000093s] fibonacci(1) -> 1
[0.00004139s] fibonacci(2) -> 1
[0.00000071s] fibonacci(1) -> 1
[0.00000082s] fibonacci(0) -> 0
[0.00000069s] fibonacci(1) -> 1
[0.00002819s] fibonacci(2) -> 1
[0.00005552s] fibonacci(3) -> 2
[0.00012448s] fibonacci(4) -> 3
[0.00000067s] fibonacci(1) -> 1
[0.00000065s] fibonacci(0) -> 0
[0.00000071s] fibonacci(1) -> 1
[0.00002738s] fibonacci(2) -> 1
[0.00005426s] fibonacci(3) -> 2
[0.00000063s] fibonacci(0) -> 0
[0.00000068s] fibonacci(1) -> 1
[0.00002711s] fibonacci(2) -> 1
[0.00000063s] fibonacci(1) -> 1
[0.00000079s] fibonacci(0) -> 0
[0.00000068s] fibonacci(1) -> 1
[0.00002817s] fibonacci(2) -> 1
[0.00005480s] fibonacci(3) -> 2
[0.00010871s] fibonacci(4) -> 3
[0.00018944s] fibonacci(5) -> 5
[0.00034368s] fibonacci(6) -> 8
8
如果使用lru装饰器:
@functools.lru_cache()
@clock
def fibonacci(n):
if n < 2:
return n
return fibonacci(n-2) + fibonacci(n-1)
这个装饰器实现了LRU(备忘录)的功能,减少了重复计算.
结果为:
[0.00000118s] fibonacci(0) -> 0
[0.00000085s] fibonacci(1) -> 1
[0.00004799s] fibonacci(2) -> 1
[0.00000145s] fibonacci(3) -> 2
[0.00007905s] fibonacci(4) -> 3
[0.00000122s] fibonacci(5) -> 5
[0.00011034s] fibonacci(6) -> 8
8
其中有装饰器的叠放的操作,装饰器的叠放的简单例子:
@d1
@d2
def f():
print('f')
等价于:
def f():
print('f')
f = d1(d2(f))
更多编程笔记文章: //github.com/lxztju/notes