Python 装饰器 @decorator
Python 装饰器其实就是对函数的包装,函数作为参数,在不修改函数源代码的基础上,并对函数做一些包装,然后返回增加了包装的函数,即生成了一个新函数。
登录校验,权限校验,日志记录等,这些功能在各个环节都可能需要,但又十分雷同,可以通过装饰器来抽象、剥离这部分代码。
def decorator_func(some_func):
# define another wrapper function which modifies some_func
def wrapper_func():
print("Wrapper function started")
some_func()
print("Wrapper function ended")
return wrapper_func
# Wrapper function add something to the passed function and decorator returns the wrapper function
def say_hello():
print ("Hello")
say_hello = decorator_func(say_hello)
say_hello()
# Output:
# Wrapper function started
# Hello
# Wrapper function ended
上面例子中,decorator_func 就是定义的装饰器,它接受 some_func 作为参数。它定义了一个 wrapper_func 函数,该函数调用了 some_func 但也增加了一些自己的代码。
装饰器的Python语法
@decorator_func
def say_hi():
print 'Hi!'
装饰器的顺序
@a
@b
@c
def foo():
print('foo')
# 等同于:
foo = a(b(c(foo)))
带参数函数的装饰器
def decorator_func(some_func):
def wrapper_func(*args, **kwargs):
print("Wrapper function started")
some_func(*args, **kwargs)
print("Wrapper function ended")
return wrapper_func
@decorator_func
def say_hi(name):
print ("Hi!" + name)
上面代码中,say_hi 函数带有一个参数。通常情况下,不同功能的函数可以有不同类别、不同数量的参数,在写wrapper_func 的时候,我们不确定参数的名称和数量,可以通过 *args 和 **kwargs 来引用函数参数。
带参数的装饰器
不仅被装饰的函数可以带参数,装饰器本身也可以带参数。参考下面的例子:
def use_logging(level):
def decorator(func):
def wrapper(*args, **kwargs):
if level == "warn":
logging.warn("%s is running" % func.__name__)
return func(*args)
return wrapper
return decorator
@use_logging(level="warn")
def foo(name='foo'):
print("i am %s" % name)
简单来说,带参数的装饰器就是在没有参数的装饰器外面再嵌套一个参数的函数,该函数返回那个无参数装饰器即可。
类作为装饰器
使用类装饰器,优点是灵活性大,高内聚,封装性。通过实现类内部的__call__方法,当使用 @ 语法糖把装饰器附加到函数上时,就会调用此方法。
class Foo(object):
def __init__(self, func):
self._func = func
def __call__(self):
print ('class decorator runing')
self._func()
print ('class decorator ending')
@Foo
def say_hi():
print('Hi!')
say_hi()
# Output:
# class decorator running
# Hi!
# class decorator ending
functools.wraps
使用装饰器极大地复用了代码,但是他有一个缺点就是原函数的元信息不见了,比如函数的docstring、name、参数列表,先看看下面例子:
def decorator_func(some_func):
def wrapper_func(*args, **kwargs):
print("Wrapper function started")
some_func(*args, **kwargs)
print("Wrapper function ended")
return wrapper_func
@decorator_func
def say_hi(name):
'''Say hi to somebody'''
print ("Hi!" + name)
print(say_hi.__name__) # Output: wrapper_func
print(say_hi.__doc__) # Output: None
可以看到,say_hi 函数被 wrapper_func 函数取代,它的 name 和 docstring 也自然是 wrapper_func 函数的了。
Python 有 functools.wraps,wraps 本身也是一个装饰器,它的作用就是把原函数的元信息拷贝到装饰器函数中,使得装饰器函数也有和原函数一样的元信息。
from functools import wraps
def decorator_func(some_func):
@wraps(some_func)
def wrapper_func(*args, **kwargs):
print("Wrapper function started")
some_func(*args, **kwargs)
print("Wrapper function ended")
return wrapper_func
@decorator_func
def say_hi(name):
'''Say hi to somebody'''
print ("Hi!" + name)
print(say_hi.__name__) # Output: say_hi
print(say_hi.__doc__) # Output: Say hi to somebody
给函数打日志
下面这个示例演示了一个 logger 的 decorator,这个 decorator 输出了函数名,参数,返回值,内存占用和运行时间。
from functools import wraps
import time
def logger(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
ts = time.time()
result = fn(*args, **kwargs)
te = time.time()
print("function = {0}".format(fn.__name__))
print(" arguments = {0} {1}".format(args, kwargs))
print(" return = {0}".format(result))
print(" time = %.6f sec" % (te-ts))
print(' memory = %d B' % fn.__sizeof__())
return result
return wrapper
@logger
def multipy(x, y):
return x * y
@logger
def sum_num(n):
s = 0
for i in range(n+1):
s += i
return s
print(multipy(2, 10))
print(sum_num(100))
print(sum_num(10000000))
类的内置装饰器
类属性 @property
静态方法 @staticmethod
类方法 @classmethod
通常,我们需要先实例化一个类的对象,再调用其方法。
若类的方法使用了 @staticmethod 或 @classmethod,就可以不需要实例化,直接类名.方法名()来调用。
从使用上来看,@staticmethod 不需要指代自身对象的self或指代自身类的cls参数,就跟使用普通函数一样。@classmethod 不需要 self 参数,但第一个参数必须是指代自身类的cls参数。如果在 @staticmethod 中要调用到这个类的一些属性方法,只能直接类名.属性名,或类名.方法名的方式。
而 @classmethod 因为持有 cls 参数,可以来调用类的属性,类的方法,实例化对象等。