Python中的装饰器(decorator)

清疚 2021-12-09 01:55 418阅读 0赞

想理解Python的decorator首先要知道在Python中函数也是一个对象,所以你可以

  • 将函数复制给变量
  • 将函数当做参数
  • 返回一个函数

函数在Python中给变量的用法一样也是一等公民,也就是高阶函数(High Order Function)。所有的魔法都是由此而来。

1,起源

我们想在函数login中输出调试信息,我们可以这样做

  1. def login():
  2. print('in login')
  3. def printdebug(func):
  4. print('enter the login')
  5. func()
  6. print('exit the login')
  7. printdebug(login)

这个方法讨厌的是每次调用login是,都通过printdebug来调用,但毕竟这是可行的。

2,让代码变得优美一点

既然函数可以作为返回值,可以赋值给变量,我们可以让代码优美一点。

  1. def login():
  2. print('in login')
  3. def printdebug(func):
  4. def __decorator():
  5. print('enter the login')
  6. func()
  7. print('exit the login')
  8. return __decorator #function as return value
  9. debug_login = printdebug(login) #function assign to variable
  10. debug_login() #execute the returned function

这样我们每次只要调用debug_login就可以了,这个名字更符合直觉。我们将原先的两个函数printdebug和login绑定到一起,成为debug_login。这种耦合叫内聚:-)。

3,让代码再优美一点

printdebug和login是通过debug_login = printdebug(login)这一句来结合的,这一句似乎也是多余的,能不能在定义login是加个标注,从而将printdebug和login结合起来?

上面的代码从语句组织的角度来讲很难再优美了,Python的解决方案是提供一个语法糖(Syntax Sugar),用一个@符号来结合它们。

  1. def printdebug(func):
  2. def __decorator():
  3. print('enter the login')
  4. func()
  5. print('exit the login')
  6. return __decorator
  7. @printdebug #combine the printdebug and login
  8. def login():
  9. print('in login')
  10. login() #make the calling point more intuitive

可以看出decorator就是一个:使用函数作参数并且返回函数的函数。通过改进我们可以得到:

  • 更简短的代码,将结合点放在函数定义时
  • 不改变原函数的函数名

在Python解释器发现login调用时,他会将login转换为printdebug(login)()。也就是说真正执行的是__decorator这个函数。

4,加上参数

1,login函数带参数

login函数可能有参数,比如login的时候传人user的信息。也就是说,我们要这样调用login:

  1. login(user)

Python会将login的参数直接传给__decorator这个函数。我们可以直接在__decorator中使用user变量:

  1. def printdebug(func):
  2. def __decorator(user): #add parameter receive the user information
  3. print('enter the login')
  4. func(user) #pass user to login
  5. print('exit the login')
  6. return __decorator
  7. @printdebug
  8. def login(user):
  9. print('in login:' + user)
  10. login('jatsz') #arguments:jatsz

我们来解释一下login(‘jatsz’)的调用过程:

[decorated] login(‘jatsz’) => printdebug(login)(‘jatsz’) => __decorator(‘jatsz’) => [real] login(‘jatsz’)

2,装饰器本身有参数

我们在定义decorator时,也可以带入参数,比如我们这样使用decorator,我们传入一个参数来指定debug level。

  1. @printdebug(level=5)
  2. def login
  3. pass

为了给接收decorator传来的参数,我们在原本的decorator上在包装一个函数来接收参数:

  1. def printdebug_level(level): #add wrapper to recevie decorator's parameter
  2. def printdebug(func):
  3. def __decorator(user):
  4. print('enter the login, and debug level is: ' + str(level)) #print debug level
  5. func(user)
  6. print('exit the login')
  7. return __decorator
  8. return printdebug #return original decorator
  9. @printdebug_level(level=5) #decorator's parameter, debug level set to 5
  10. def login(user):
  11. print('in login:' + user)
  12. login('jatsz')

我们再来解释一下login(‘jatsz’)整个调用过程:

[decorated]login(‘jatsz’) => printdebug_level(5) => printdebug[with closure value 5](login)(‘jatsz’) => __decorator(‘jatsz’)[use value 5] => [real]login(‘jatsz’)

5,装饰有返回值的函数

有时候login会有返回值,比如返回message来表明login是否成功。

  1. login_result = login(‘jatsz’)

我们需要将返回值在decorator和调用函数间传递:

  1. def printdebug(func):
  2. def __decorator(user):
  3. print('enter the login')
  4. result = func(user) #recevie the native function call result
  5. print('exit the login')
  6. return result #return to caller
  7. return __decorator
  8. @printdebug
  9. def login(user):
  10. print('in login:' + user)
  11. msg = "success" if user == "jatsz" else "fail"
  12. return msg #login with a return value
  13. result1 = login('jatsz');
  14. print result1 #print login result
  15. result2 = login('candy');
  16. print result2

我们解释一下返回值的传递过程:

…omit for brief…[real][msg from login(‘jatsz’) => [result from]__decorator => [assign to] result1

6,应用多个装饰器

我们可以对一个函数应用多个装饰器,这时我们需要留心的是应用装饰器的顺序对结果会产生。影响比如:

  1. def printdebug(func):
  2. def __decorator():
  3. print('enter the login')
  4. func()
  5. print('exit the login')
  6. return __decorator
  7. def others(func): #define a other decorator
  8. def __decorator():
  9. print '***other decorator***'
  10. func()
  11. return __decorator
  12. @others #apply two of decorator
  13. @printdebug
  14. def login():
  15. print('in login:')
  16. @printdebug #switch decorator order
  17. @others
  18. def logout():
  19. print('in logout:')
  20. login()
  21. print('---------------------------')
  22. logout()

我们定义了另一个装饰器others,然后我们对login函数和logout函数分别应用这两个装饰器。应用方式很简单,在函数定义是直接用两个@@就可以了。我们看一下上面代码的输出:

  1. $ python deoc.py
  2. ***other decorator***
  3. enter the login
  4. in login:
  5. exit the login
  6. ---------------------------
  7. enter the login
  8. ***other decorator***
  9. in logout:
  10. exit the login

我们看到两个装饰器都已经成功应用上去了,不过输出却不相同。造成这个输出不同的原因是我们应用装饰器的顺序不同。回头看看我们login的定义,我们是先应用others,然后才是printdebug。而logout函数真好相反,发生了什么?如果你仔细看logout函数的输出结果,可以看到装饰器的递归。从输出可以看出:logout函数先应用printdebug,打印出“enter the login”。printdebug的__decorator调用中间应用了others的__decorator,打印出“***other decorator***”。其实在逻辑上我们可以将logout函数应用装饰器的过程这样看(伪代码):

  1. @printdebug #switch decorator order
  2. (
  3. @others
  4. (
  5. def logout():
  6. print('in logout:')
  7. )
  8. )

我们解释一下整个递归应用decorator的过程:

[printdebug decorated]logout() =>

printdebug.__decorator[call [others decorated]logout() ] =>

printdebug.__decorator.other.__decorator[call real logout]

7,灵活运用

什么情况下装饰器不适用?装饰器不能对函数的一部分应用,只能作用于整个函数。

login函数是一个整体,当我们想对部分函数应用装饰器时,装饰器变的无从下手。比如我们想对下面这行语句应用装饰器:

  1. msg = "success" if user == "jatsz" else "fail"

怎么办?

一个变通的办法是“提取函数”,我们将这行语句提取成函数,然后对提取出来的函数应用装饰器:

  1. def printdebug(func):
  2. def __decorator(user):
  3. print('enter the login')
  4. result = func(user)
  5. print('exit the login')
  6. return result
  7. return __decorator
  8. def login(user):
  9. print('in login:' + user)
  10. msg = validate(user) #exact to a method
  11. return msg
  12. @printdebug #apply the decorator for exacted method
  13. def validate(user):
  14. msg = "success" if user == "jatsz" else "fail"
  15. return msg
  16. result1 = login('jatsz');
  17. print result1

来个更加真实的应用,有时候validate是个耗时的过程。为了提高应用的性能,我们会将validate的结果cache一段时间(30 seconds),借助decorator和上面的方法,我们可以这样实现:

  1. import time
  2. dictcache = {}
  3. def cache(func):
  4. def __decorator(user):
  5. now = time.time()
  6. if (user in dictcache):
  7. result,cache_time = dictcache[user]
  8. if (now - cache_time) > 30: #cache expired
  9. result = func(user)
  10. dictcache[user] = (result, now) #cache the result by user
  11. else:
  12. print('cache hits')
  13. else:
  14. result = func(user)
  15. dictcache[user] = (result, now)
  16. return result
  17. return __decorator
  18. def login(user):
  19. print('in login:' + user)
  20. msg = validate(user)
  21. return msg
  22. @cache #apply the cache for this slow validation
  23. def validate(user):
  24. time.sleep(5) #simulate 10 second block
  25. msg = "success" if user == "jatsz" else "fail"
  26. return msg
  27. result1 = login('jatsz'); print result1
  28. result2 = login('jatsz'); print result2 #this login will return immediately by hit the cache
  29. result3 = login('candy'); print result3

Reference:

http://stackoverflow.com/questions/739654/understanding-python-decorators —Understanding Python decorators

http://www.cnblogs.com/rhcad/archive/2011/12/21/2295507.html —Python装饰器学习(九步入门)

http://www.python.org/dev/peps/pep-0318/ —PEP 318 — Decorators for Functions and Methods

转载于:https://www.cnblogs.com/Jerry-Chou/archive/2012/05/23/python-decorator-explain.html

发表评论

表情:
评论列表 (有 0 条评论,418人围观)

还没有评论,来说两句吧...

相关阅读