为什么需要装饰器
先看两个例子
def one_hello():
print ("hello!")
def two_hello():
print ("hello!")
if __name__ == '__main__':
one_hello()
two_hello()
如果我们要调试这两个函数,有时候会要求调用每个方法前都要记录进入函数的名称,可以最原始的方法如下:
def one_hello():
print "[DEBUG]: enter one_hello()"
print ("hello!")
def two_hello():
print "[DEBUG]: enter two_hello()"
print ("hello!")
if __name__ == '__main__':
one_hello()
two_hello()
也可以使用inspect模块:
def debug():
import inspect
caller_name = inspect.stack()[1][3]
print "[DEBUG]: enter {}()".format(caller_name)
def one_hello():
debug()
print ("hello!")
def two_hello():
debug()
print ("hello!")
if __name__ == '__main__':
one_hello()
two_hello()
看起来效果是好了一点,但是每个函数都是在内部,如果代码量很大,注释也没有写好,就很难发觉某个函数增加了某个函数的功能,简单来说就是不够直白。这就需要引入装饰器的概念了。
什么是装饰器
装饰器是一个用于封装函数或类的代码的工具。它显示地将封装器应用到函数或类上,从而使它们选择加入到装饰器的功能中。装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。
怎么写一个装饰器
针对上一个问题可以有如下方法:
def debug(func):
def wrapper():
print "[DEBUG]: enter {}()".format(func.__name__)
return func()
return wrapper
@debug
def hello():
print ("hello!")
这种功能类似于hello = debug(hello) # 添加功能并保持原函数名不变
,但是目前还不能接受参数,需要进一步的改进:
def debug(func):
def wrapper(*args, **kwargs): # 指定宇宙无敌参数
print ("[DEBUG]: enter {}()".format(func.__name__))
print ('Prepare and say...',)
return func(*args, **kwargs)
return wrapper # 返回
@debug
def say(something):
print ("hello {}!".format(something))
带参数的装饰器
假设我们前文的装饰器需要完成的功能不仅仅是能在进入某个函数后打出log信息,而且还需指定log的级别,那么装饰器就会是这样的。
# coding=utf-8
def logging(level):
def wrapper(func):
def inner_wrapper(*args, **kwargs):
print ("[{level}]: enter function {func}()".format(
level=level,
func=func.__name__))
return func(*args, **kwargs)
return inner_wrapper
return wrapper
@logging(level='INFO')
def say():
print ("say {}!")
# 如果没有使用@语法,等同于
# say = logging(level='INFO')(say)
@logging(level='DEBUG')
def do():
print ("do {}...")
if __name__ == '__main__':
say()
do()
是不是有一些晕?你可以这么理解,当带参数的装饰器被打在某个函数上时,比如@logging(level='DEBUG')
,它其实是一个函数,会马上被执行,只要这个它返回的结果是一个装饰器时,那就没问题。细细再体会一下。这个过程相当于压栈,先调用装饰器函数,然后装饰器参数进栈,而后被装饰的函数入栈,最后就是被装饰函数的参数入栈,传递到具体装饰器时,就按出栈顺序进行传递。
装饰器与类
装饰的类
装饰器函数其实是这样一个接口约束,它必须接受一个callable对象作为参数,然后返回一个callable对象。在Python中一般callable对象都是函数,但也有例外。只要某个对象重载了__call__()
方法,那么这个对象就是callable的。
class Test():
def __call__(self):
print ('call me!')
t = Test()
t() # call me
像__call__
这样前后都带下划线的方法在Python中被称为内置方法,有时候也被称为魔法方法。重载这些魔法方法一般会改变对象的内部行为。上面这个例子就让一个类对象拥有了被调用的行为。
回到装饰器上的概念上来,装饰器要求接受一个callable对象,并返回一个callable对象(不太严谨,详见后文)。那么用类来实现也是也可以的。我们可以让类的构造函数__init__()
接受一个函数,然后重载__call__()
并返回一个函数,也可以达到装饰器函数的效果。
class logging(object):
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print ("[DEBUG]: enter function {func}()".format(
func=self.func.__name__))
return self.func(*args, **kwargs)
@logging
def say():
print ("say!")
say()
被装饰的类
import functools
import time
def sortable_by_creation_time(cls):
original_init = cls.__init__
@functools.wraps(original_init)
def new_init(self, *args, **kwargs):
original_init(self, *args, **kwargs)
self._created = time.time()
cls.__init__ = new_init
def new_lt(self, other):
print("sort lt")
return self._created < other._created
cls.__lt__ = new_lt
#cls.__lt__ = lambda self, other: self._created < other._created
#cls.__gt__ = lambda self, other: self._created > other._created
return cls
@sortable_by_creation_time
class Sortable(object):
def __init__(self, identifier):
self.identifier = identifier
def __repr__(self):
return self.identifier
def __lt__(self, other):
print('self lt')
return self.identifier < other.identifier
first = Sortable('first')
second = Sortable('second')
third = Sortable('third')
print(first._created)
sortables = [third, first, second]
sortables.sort()
print(sortables)
装饰器带来的函数与类的类型转换
# coding=utf-8
class Task():
def run(self, *args, **kwargs):
raise NotImplementedError('Subclasses must implement `run`.')
def identify(self):
return "I am a task"
def __call__(self, *args, **kwargs):
return self.run(*args, **kwargs)
def task(func):
class TaskSubclass(Task):
def run (self, *args, **kwargs):
return func(*args, **kwargs)
return TaskSubclass()
@task
def foo():
return 2 + 2
print(foo())
print(foo.identify())
以上的根本是:对于一个装饰器来说唯一的要求就是返回一个可调用的函数,即可以以name()
形式就行调用。