详解Python装饰器
函数装饰器用于在源码中“标记”函数,以某种方式增强函数的行为。这是一项强大的功能,但是若想掌握,必须理解闭包。
闭包
在了解装饰器之前先讲一下闭包,闭包是一种特殊的函数,这种函数由多个函数的嵌套组成,且称之为外函数和内函数,外函数返回值是内函数的引用,此时就构成了闭包。构建一个闭包有以下几点要求: 1. 多个函数的嵌套 2. 必须返回一个函数对象 3. 返回的那个函数必须引用外部变量 闭包的优点: - 闭包可以保存运行环境,即在闭包内的变量是不能被轻易修改的; - 闭包的好处:提高代码的可复用性。 举个栗子:
def out_func(n):
def in_func(num):
return n*num
return in_func
demo = out_func(3)
res = demo(4)
print(res)
装饰器
装饰器也是一种闭包,只不过它的参数是被装饰的函数。举个栗子,定义一个装饰器,计算函数的执行时间:
import time
# 定义装饰器
def time_calc(func):
def wrapper(*args, **kargs):
start_time = time.time()
f = func(*args,**kargs)
exec_time = time.time() - start_time
return f
return wrapper
# 使用装饰器
@time_calc
def add(a, b):
return a + b
@time_calc
def sub(a, b):
return a - b
上述的装饰器与add = time_calc(add)
含义相同。但是这样的装饰器仍有缺陷,执行print(add.__name__)
语句会发现输出的并不是add
,而是wrapper
,是因为这个函数重写了add
的名字和注释文档。幸运的是,Python中functools.wraps
函数可以解决这个问题。
from functools import wraps
def time_calc(func):
@wraps(func)
def wrapper(*args, **kargs):
start_time = time.time()
f = func(*args,**kargs)
exec_time = time.time() - start_time
print('RunTime',exec_time)
return f
return wrapper
@time_calc
def add(a, b):
return a + b
而上面的例子除了可以用函数来定义,也可以用类来定义。
from functools import wraps
class time_calc(object):
def __call__(self,func):
@wraps(func)
def wrapper(*args, **kargs):
start_time = time.time()
f = func(*args,**kargs)
exec_time = time.time() - start_time
print('RunTime',exec_time)
return f
return wrapper
def notify(self):
pass
@time_calc()
def add(a, b):
return a + b
再来个与它类似的,找不同! from functools import wraps
class time_calc(object):
def __init__(self, func):
self._func = func
def __call__(self):
@wraps(self._func)
def wrapper(*args, **kargs):
start_time = time.time()
f = self._func(*args,**kargs)
exec_time = time.time() - start_time
print('RunTime',exec_time)
return f
return wrapper
def notify(self):
pass
@time_calc
def add(a, b):
return a + b
内置装饰器
常见的有三种:@property
、@staticmethod
、@classmethod
@property
把类内方法当成属性来使用,必须要有返回值,相当于getter;假如没有定义 @func.setter 修饰方法的话,就是只读属性
# 将 property 函数用作装饰器可以很方便的创建只读属性
# 下面的代码将 voltage() 方法转化成同名只读属性的 getter 方法。
class Parrot(object):
def __init__(self):
self._voltage = 100000
@property
def voltage(self):
"""Get the current voltage."""
return self._voltage
# property 的 getter,setter 和 deleter 方法同样可以用作装饰器
# 下面两个例子是完全相同的
class C(object):
def __init__(self):
self._x = None
def getx(self):
return self._x
def setx(self, value):
self._x = value
def delx(self):
del self._x
x = property(getx, setx, delx, "I'm the 'x' property.")
class C(object):
def __init__(self):
self._x = None
@property
def x(self):
"""I'm the 'x' property."""
return self._x
@x.setter
def x(self, value):
self._x = value
@x.deleter
def x(self):
del self._x
@staicmethod
静态方法,不需要表示自身对象的self和自身类的cls参数,就跟使用函数一样。
@classmethod
类方法,不需要self参数,但第一个参数需要是表示自身类的cls参数。
class Demo(object):
text = "三种方法的比较"
def instance_method(self):
print("调用实例方法")
@classmethod
def class_method(cls):
print("调用类方法")
print("在类方法中 访问类属性 text: {}".format(cls.text))
print("在类方法中 调用实例方法 instance_method: {}".format(cls().instance_method()))
@staticmethod
def static_method():
print("调用静态方法")
print("在静态方法中 访问类属性 text: {}".format(Demo.text))
print("在静态方法中 调用实例方法 instance_method: {}".format(Demo().instance_method()))
if __name__ == "__main__":
# 实例化对象
d = Demo()
# 对象可以访问 实例方法、类方法、静态方法
# 通过对象访问text属性
print(d.text)
# 通过对象调用实例方法
d.instance_method()
# 通过对象调用类方法
d.class_method()
# 通过对象调用静态方法
d.static_method()
# 类可以访问类方法、静态方法
# 通过类访问text属性
print(Demo.text)
# 通过类调用类方法
Demo.class_method()
# 通过类调用静态方法
Demo.static_method()
区别
在定义静态类方法和类方法时,@staticmethod 装饰的静态方法里面,想要访问类属性或调用实例方法,必须需要把类名写上; 而@classmethod装饰的类方法里面,会传一个cls参数,代表本类,这样就能够避免手写类名的硬编码。 在调用静态方法和类方法时,实际上写法都差不多,一般都是通过 类名.静态方法() 或 类名.类方法()。 也可以用实例化对象去调用静态方法和类方法,但为了和实例方法区分,最好还是用类去调用静态方法和类方法。
使用场景
假如不需要用到与类相关的属性或方法时,就用静态方法@staticmethod; 假如需要用到与类相关的属性或方法,然后又想表明这个方法是整个类通用的,而不是对象特异的,就可以使用类方法@classmethod。