在Python中,装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。概括的讲,装饰器的作用就是为已经存在的函数或对象添加额外的功能。
下面是一个最简单的不带参数的装饰器,其输入就是一个函数名。
def debug(func):
def wrapper(funcarg):
print("Debug: " + func.__name__ + "...")
return func(funcarg)
return wrapper
@debug
def say_hello(name):
print("hello, " + name + "!")
if __name__ == '__main__':
say_hello("qinxue")
如果需要传递很多参数,那么就将里面的wrapper函数的参数改成统一的、针对任何情况的参数。
def debug(func):
def wrapper(*args, **kwargs):
print("Debug: " + func.__name__ + "...")
return func(*args, **kwargs)
return wrapper
@debug
def say_hello(name, age):
print("hello, " + name + "!" + "your age is: " + str(age))
if __name__ == '__main__':
say_hello("qinxue", 24)
带参数的装饰器,这时,就要进行两层封装。第一层为带参数的装饰器函数,第二层为将函数名作为参数的wrapper函数。
def logging(level):
def wrapper(func):
def inner_wrapper(*args, **kwargs):
print(level + ":" + "function: " + func.__name__ + "...")
return func(*args, **kwargs)
return inner_wrapper
return wrapper
@logging(level='INFO')
def say(something):
print("info: " + something)
@logging(level='DEBUG')
def do(something):
print("debug: " + something)
if __name__ == '__main__':
say("success!")
do("opps, something wrong!")
可以这么理解,当带参数的装饰器被打在某个函数上时,比如@logging(level='DEBUG')
,它其实是一个函数,会马上被执行,只要这个它返回的结果是一个装饰器时,那就没问题。
class Student():
def __call__(self):
print("I am a student!")
s = Student()
s() #I am a student!
这是用类来实现的像__call__
这样前后都带下划线的方法在Python中被称为内置方法,有时候也被称为魔法方法。重载这些魔法方法一般会改变对象的内部行为。上面这个例子就让一个类对象拥有了被调用的行为。
回到装饰器上的概念上来,装饰器要求接受一个callable对象。那么用类来实现也是也可以的。可以让类的构造函数__init__()
接受一个函数,然后重载__call__()
并返回一个函数,也可以达到装饰器函数的效果。
class logging(object):
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print("debug: " + self.func.__name__)
return self.func(*args, **kwargs)
@logging
def greeting(name):
print("hello, " + name)
if __name__ == '__main__':
greeting("qinxue")
带参数的类装饰器
class logging(object):
def __init__(self, level="INFO"):
self.level = level
def __call__(self, func):
def wrapper(*args, **kwargs):
print(self.level + ": func: " + func.__name__)
func(*args, **kwargs)
return wrapper
@logging(level="INFO")
def greeting(name):
print("hello, " + name)
if __name__ == '__main__':
greeting("qinxue")
property是一种特殊的属性,访问它时会执行一段功能(函数)然后返回值。将一个类的函数定义成特性以后,对象再去使用的时候obj.name,根本无法察觉自己的name是执行了一个函数然后计算出来的,这种特性的使用方式遵循了统一访问的原则
import math
class Circle:
def __init__(self, radius): #圆的半径radius
self.radius = radius
@property
def area(self):
return math.pi * self.radius**2 #计算面积
@property
def perimeter(self):
return 2*math.pi*self.radius #计算周长
c = Circle(10)
print(c.radius)
print(c.area) #可以像访问数据属性一样去访问area,会触发一个函数的执行,动态计算出一个值
print(c.perimeter) #同上
对于classmethod(类方法):不管这个方式是从实例调用还是从类调用,它都用第一个参数把类传递过来。具体来说,类和对象都可以调用它。
对于staticmethod(静态方法):经常有一些跟类有关系的功能但在运行时又不需要实例和类参与的情况下需要用到静态方法. 比如更改环境变量或者修改其他类的属性等能用到静态方法。这个其实和C++中的静态函数、静态变量很类似的。同样的,类和对象都可以调用它。
class Student(object):
def __init__(self, name):
self.name = name
def printName(self):
print(self.name)
@staticmethod
def smethod(*arg):
print("Statics: ", arg)
@classmethod
def cmethod(*arg):
print("Class: ", arg)
s = Student("qinxue")
s.printName()# qinxue
Student.printName()# 会报错,因为类不可以直接调用非静态函数
s.smethod()# Statics: ()
Student.smethod()# Statics: ()
s.cmethod()# Class: (<class '__main__.Student'>,)
Student.cmethod()# Class: (<class '__main__.Student'>,)
当有很多装饰器来装饰一个函数时,其调用过程如下:
@a
@b
@c
def f():
等效于
f = a(b(c(f)))
示例如下:
from functools import wraps
def a(func):
@wraps(func)
def wrapper(*args, **kwargs):
print("a: " + func.__name__ + "...")
return func(*args, **kwargs)
return wrapper
def b(func):
@wraps(func)
def wrapper(*args, **kwargs):
print("b: " + func.__name__ + "...")
return func(*args, **kwargs)
return wrapper
def c(func):
@wraps(func)
def wrapper(*args, **kwargs):
print("c: " + func.__name__ + "...")
return func(*args, **kwargs)
return wrapper
@a
@b
@c
def f(name):
print("hello, " + name)
if __name__ == '__main__':
f("qinxue")
# 输出的结果为:
# a: f...
# b: f...
# c: f...
# hello, qinxue
以上就是对python中装饰器的简单总结。
参考文章如下:
https://www.zhihu.com/question/26930016
https://www.cnblogs.com/cicaday/p/python-decorator.html
https://www.cnblogs.com/wangyongsong/p/6750454.html
https://www.pythoncentral.io/difference-between-staticmethod-and-classmethod-in-python/