[关闭]
@magine 2015-02-02T11:37:22.000000Z 字数 2083 阅读 1252

实习日记 2015年2月1日 (装饰器入门教程

实习日记 装饰器


什么是装饰器?

装饰器是一种设计模式,它可以将对象或函数的真正功能与其他辅助的功能分离开。

一个简单的装饰器模样如下:

  1. def decorator(f):
  2. # before decorate function
  3. def decorated_f(*args, **kwargs):
  4. # do something with args, kwargs and the function f
  5. return f(*args, **kwargs)
  6. # after decorate function
  7. return decorated_f

如何使用?

容易理解但是丑陋的写法(不要这么写,真的很low!)

装饰器本身就是函数,所以你可以直接用其返回值覆盖原有的函数。

  1. def func(*args, **kwargs):
  2. # do something
  3. func = decorator(func)

此处使用这种写法是为了便于大家理解装饰器的工作原理(如果你能读懂上面两个代码片段,就不要浪费时间看下面那段废话和这句废话):

func这个函数被作为参数传入到decorator函数中,该函数内部定义了一个函数decorated_fdecorated_f在对传入的参数(即argskwargs)以及func函数做一些处理后,把处理后的参数传递给func,并返回它的执行结果(注意,是执行结果,不是函数本体)。

CAUTION: 以上对于装饰器的定义,注意以下两点:

使用语法糖 ‘@’(一般大家都会这么写)

  1. @decorator
  2. def func(*args, **kwargs):
  3. # do something

这种写法与上一种完全等价,但是你应该尽量使用这一种,因为Python之禅说过:

Beautiful is better than ugly.

补充一点,装饰器可以叠加使用:

  1. @decorator3
  2. @decorator2
  3. @decorator1
  4. def func(*args, **kwargs):
  5. # do something

编号越小(即越靠近函数),越先被调用。

我要传参数!

诚如上文所见,一个标准的装饰器是不能传入参数的。如果想要装饰器更灵活一点,你可以再次为它加上一层“包装”(它本身就包装了函数,不是么?),或者说你需要调用的是一个生成装饰器的函数(decorator factory)。

就像这个样子:

  1. # 注意我在第2行与第4行的独特传参方式,当然你不一定非要这样写
  2. def factory(*args, **kwargs):
  3. def decorator(f):
  4. def decorated_f(*args, **kwargs):
  5. # do something with args, kwargs and the function f
  6. return f(*args, **kwargs)
  7. return decorated_f
  8. return decorator
  9. @factory(*args, **kwargs)
  10. def func(*args, **kwargs):
  11. # do something

那么去掉语法糖应该写成什么样呢?嗯,它看起来有点儿古怪:

  1. def func(*args, **kwargs):
  2. # do something
  3. func = factory(*args, **kwargs)(func)

我想无需多说,你已经可以自己判断出哪些部分是在装饰时运行,哪些部分是在函数调用时运行。

functools.wraps()

调用经过装饰的函数,相当于调用一个新函数,那么查看函数参数、注释、甚至函数名的时候,就只能看到装饰器的相关信息,而被包装函数的信息则被丢掉了。

别担心,wraps()可以帮你转移这些信息。

  1. from functools import wraps
  2. # 不含参数的
  3. def decorator(f):
  4. @wraps(f)
  5. def decorated_f(*args, **kwargs):
  6. # do something with args, kwargs and the function f
  7. return f(*args, **kwargs)
  8. return decorated_f
  9. # 含参数的
  10. def factory(*args, **kwargs):
  11. def decorator(f):
  12. @wraps(f)
  13. def decorated_f(*args, **kwargs):
  14. # do something with args, kwargs and the function f
  15. return f(*args, **kwargs)
  16. return decorated_f
  17. return decorator

补充知识点

关于functools.wraps()的工作原理,想知道更多吗?

装饰器的深入学习

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注