深入了解Python装饰器的高级用法


Posted in Python onAugust 13, 2020

原文地址
https://www.codementor.io/python/tutorial/advanced-use-python-decorators-class-function

介绍

我写这篇文章的主要目的是介绍装饰器的高级用法。如果你对装饰器知之甚少,或者对本文讲到的知识点易混淆。我建议你复习下装饰器基础教程。
本教程的目标是介绍装饰器的一些有趣的用法。特别是怎样在类中使用装饰器,怎样给装饰器传递额外的参数。

装饰器 vs 装饰器模式

Decorator模式是一个面向对象的设计模式,它允许动态地往现有的对象添加行为。当你装饰了一个对象,在某种程度上,你是在独立于同一个类的其他实例的基础上扩展其功能。
Python装饰器不是装饰器模式的实现,它在函数、方法定义的时候添加功能,而不是在运行的时候添加。Decorator设计模式本身可以在Python中实现,因为Python是动态编程语言,所以没有必要这样做。

一个基础的装饰器

这是装饰器的最简单例子,在继续往下面阅读之前请确保理解此段代码。如果你需要更多关于此代码的解释,请复习下基础装饰器教程。

def time_this(original_function): 
  def new_function(*args, **kwargs):
    import datetime 
    before = datetime.datetime.now() 
    x = original_function(*args, **kwargs) 
    after = datetime.datetime.now() 
    print("Elapsed Time = {}".format(after-before)) 
    return x 
  return new_function
@time_this
def func_a(stuff): 
  import time 
  time.sleep(stuff) 
  func_a(3)
# out:
Elapsed Time = 0:00:03.012472

带参数的装饰器

有时候带参数的装饰器会非常有用,这种技术经常用在函数注册中。在web框架Pyramid中经常有用到,例如:

@view_config(route_name='home', renderer='templates/mytemplate.pt')
def my_view(request): 
  return {'project': 'hello decorators'}

比方说,我们有一个用户可以登录并且可以和用户交互的GUI应用程序。用户和GUI界面的交互触发事件,导致Python函数执行。假设有许多使用该图形界面的用户,他们各自的权限级别差异很大,不同的功能执行需要不同的权限。比如,考虑以下功能:

# 假设这些函数是存在的
def current_user_id(): 
  """ this function returns the current logged in user id, if the use is not authenticated the return None """
def get_permissions(iUserId): 
  """ returns a list of permission strings for the given user. For example ['logged_in','administrator','premium_member'] """
# 在这些函数中我们需要实现权限检查 
def delete_user(iUserId): 
  """ delete the user with the given Id. This function is only accessable to users with administrator permissions """ 
def new_game(): 
  """ any logged in user can start a new game """ 
def premium_checkpoint(): 
  """ save the game progress, only accessable to premium members """

一种实现这些权限检查的方式是实现多个装饰器,比如:

def requires_admin(fn): 
  def ret_fn(*args,**kwargs): 
    lPermissions = get_permissions(current_user_id()) 
    if 'administrator' in lPermissions: 
      return fn(*args,**kwargs) 
    else: raise Exception("Not allowed") 
  return ret_fn
def requires_logged_in(fn): 
  def ret_fn(*args,**kwargs): 
    lPermissions = get_permissions(current_user_id()) 
    if 'logged_in' in lPermissions: 
      return fn(*args,**kwargs) 
    else: 
      raise Exception("Not allowed") 
    return ret_fn 
def requires_premium_member(fn): 
  def ret_fn(*args,**kwargs): 
    lPermissions = get_permissions(current_user_id()) 
    if 'premium_member' in lPermissions: 
      return fn(*args,**kwargs) 
    else: 
      raise Exception("Not allowed") 
    return ret_fn 
@requires_admin
def delete_user(iUserId): 
""" delete the user with the given Id. This function is only accessable to users with administrator permissions """
@requires_logged_in
def new_game(): 
""" any logged in user can start a new game """ @requires_premium_member
def premium_checkpoint(): 
""" save the game progress, only accessable to premium members """

但是,这太可怕了。这需要大量的复制粘贴,每个装饰器需要一个不同的名字,如果有任何关于权限检查的改变,每个装饰器都需要修改。就没有一个装饰器把以上三个装饰器的工作都干了的吗?

为了解决此问题,我们需要一个返回装饰器的函数:

def requires_permission(sPermission): 
  def decorator(fn): 
    def decorated(*args,**kwargs): 
      lPermissions = get_permissions(current_user_id()) 
      if sPermission in lPermissions: 
        return fn(*args,**kwargs)
      raise Exception("permission denied") 
    return decorated 
  return decorator
def get_permissions(iUserId): 
  # this is here so that the decorator doesn't throw NameErrors 
  return ['logged_in',]
def current_user_id(): 
  #ditto on the NameErrors 
  return 1
#and now we can decorate stuff... 
@requires_permission('administrator')
def delete_user(iUserId): 
""" delete the user with the given Id. This function is only accessible to users with administrator permissions """
@requires_permission('logged_in')
def new_game(): 
""" any logged in user can start a new game """ @requires_permission('premium_member')
def premium_checkpoint(): 
""" save the game progress, only accessable to premium members """

尝试一下调用delete_usernew namepremium_checkpoint然后看看发生了什么。
premium_checkpointdelete_user 产生了一个“permission denied”的异常,new_game执行正常。
下面是带参数装饰的一般形式,和例子的使用:

def outer_decorator(*outer_args,**outer_kwargs): 
  def decorator(fn): 
    def decorated(*args,**kwargs):
      do_something(*outer_args,**outer_kwargs) 
      return fn(*args,**kwargs) 
    return decorated 
  return decorator 
@outer_decorator(1,2,3)
def foo(a,b,c): 
  print(a) 
  print(b) 
  print(c)
foo()

等价于:

def decorator(fn): 
  def decorated(*args,**kwargs): 
    do_something(1,2,3) 
    return fn(*args,**kwargs) 
  return decorated 
return decorator 
@decorator
def foo(a,b,c): 
  print(a)
  print(b) 
  print(c)
foo()

类装饰器

装饰器不仅可以修饰函数,还可以对类进行装饰。比如说,我们有一个类,该类含有许多重要的方法,我们需要记录每一个方法执行的时间。我们可以使用上述的time_this装饰此类:

class ImportantStuff(object): 
@time_this 
def do_stuff_1(self): 
  pass
@time_this 
def do_stuff_2(self): 
  pass
@time_this 
def do_stuff_3(self): 
  pass

此方法可以运行正常。但是在该类中存在许多多余的代码,如果我们想建立更多的类方法并且遗忘了装饰其中的一个方法,如果我们不想装饰该类中的方法了,会发生什么样的情况呢?这可能会存在出现认为错误的空间,如果写成这样会更有好:

@time_all_class_methods
class ImportantStuff: 
  def do_stuff_1(self):
    pass
  def do_stuff_2(self):
    pass
  def do_stuff_3(self):
    pass

等价于:

class ImportantStuff: 
  def do_stuff_1(self):
    pass
  def do_stuff_2(self):
    pass
  def do_stuff_3(self):
    pass
ImportantStuff = time_all_class_methods(ImportantStuff)

那么time_all_class_methods是怎么工作的呢?
首先,我们需要采用一个类作为参数,然后返回一个类,我们也要知道返回的类的功能应该和原始类ImportantStuff功能一样。也就是说,我们仍然希望做重要的事情,我们希望记录下每个步骤发生的时间。我们写成这样:

def time_this(original_function): 
  print("decorating") 
  def new_function(*args,**kwargs): 
    print("starting timer") 
    import datetime 
    before = datetime.datetime.now() 
    x = original_function(*args,**kwargs) 
    after = datetime.datetime.now() 
    print("Elapsed Time = {0}".format(after-before)) 
    return x 
  return new_function
def time_all_class_methods(Cls): 
  class NewCls: 
    def __init__(self,*args,**kwargs): 
      self.oInstance = Cls(*args,**kwargs) 
    def __getattribute__(self,s): 
      try: 
        x = super(NewCls,self).__getattribute__(s) 
      except AttributeError: 
        pass 
      else: 
        return x 
      x = self.oInstance.__getattribute__(s) 
      if type(x) == type(self.__init__): 
        return time_this(x) 
      else: 
        return x 
    return NewCls
@time_all_class_methods
class Foo: 
  def a(self): 
    print("entering a") 
    import time 
    time.sleep(3) 
    print("exiting a")
oF = Foo()
oF.a()
# out:
decorating
starting timer
entering a
exiting a
Elapsed Time = 0:00:03.006767

总结

在此篇教程中,我们给大家展示了一些Python装饰器使用的技巧-我们介绍了怎么样把参数传递给装饰器,怎样装饰类。但是这仅仅是冰山一角。除了本文介绍的之外,还有其他好多装饰器的使用方法,我们甚至可以使用装饰器装饰装饰器(如果你有机会使用到它,这可能是一个做全面检查的好方法)。Python有一些内置的装饰器,比如:staticmethodclassmethod
阅读完本文还需要学习什么呢?通常是没有比我在文章中展示的装饰器更复杂的了,如果你有兴趣学习更多关于改变类功能的方法,我建议您阅读下继承和OOP设计原则。或者你可以试试阅读一下元类。

以上就是深入了解Python装饰器的高级用法的详细内容,更多关于Python装饰器的资料请关注三水点靠木其它相关文章!

Python 相关文章推荐
简明 Python 基础学习教程
Feb 08 Python
python中list循环语句用法实例
Nov 10 Python
python实现复制整个目录的方法
May 12 Python
Python while、for、生成器、列表推导等语句的执行效率测试
Jun 03 Python
Python列表切片用法示例
Apr 19 Python
Python+matplotlib+numpy绘制精美的条形统计图
Jan 02 Python
在Python中构建增广矩阵的实现方法
Jul 01 Python
python输入错误后删除的方法
Oct 12 Python
windows中安装Python3.8.0的实现方法
Nov 19 Python
Pytorch中的VGG实现修改最后一层FC
Jan 15 Python
Python自定义聚合函数merge与transform区别详解
May 26 Python
Python如何实现定时器功能
May 28 Python
python高级特性简介
Aug 13 #Python
Pytest如何使用skip跳过执行测试
Aug 13 #Python
matplotlib基础绘图命令之bar的使用方法
Aug 13 #Python
Python logging模块原理解析及应用
Aug 13 #Python
matplotlib基础绘图命令之imshow的使用
Aug 13 #Python
使用jupyter notebook运行python和R的步骤
Aug 13 #Python
matplotlib基础绘图命令之errorbar的使用
Aug 13 #Python
You might like
PHP jQuery表单,带验证具体实现方法
2014/02/15 PHP
php实现最简单的MVC框架实例教程
2014/09/08 PHP
PHP中imagick函数的中文解释
2015/01/21 PHP
javascript静态的url如何传递
2007/05/03 Javascript
javascript的trim,ltrim,rtrim自定义函数
2008/09/21 Javascript
javascript写的简单的计算器,内容很多,方法实用,推荐
2011/12/29 Javascript
JS实现仿百度输入框自动匹配功能的示例代码
2014/02/19 Javascript
express的中间件bodyParser详解
2014/12/04 Javascript
JavaScript中switch语句的用法详解
2015/06/03 Javascript
JS button按钮实现submit按钮提交效果
2016/11/01 Javascript
小程序登录/注册页面设计的实现代码
2019/05/24 Javascript
MockJs结合json-server模拟后台数据
2020/08/26 Javascript
jquery中attr、prop、data区别与用法分析
2019/09/25 jQuery
vue-router 中 meta的用法详解
2019/11/01 Javascript
webpack中的模式(mode)使用详解
2020/02/20 Javascript
5个你不知道的JavaScript字符串处理库(小结)
2020/06/01 Javascript
JavaScript中展开运算符及应用的实例代码
2021/01/14 Javascript
Python中的二叉树查找算法模块使用指南
2014/07/04 Python
Tornado Web服务器多进程启动的2个方法
2014/08/04 Python
python判断一个集合是否包含了另外一个集合中所有项的方法
2015/06/30 Python
对dataframe数据之间求补集的实例详解
2019/01/30 Python
python批量修改文件夹及其子文件夹下的文件内容
2019/03/15 Python
python查询文件夹下excel的sheet名代码实例
2019/04/02 Python
cProfile Python性能分析工具使用详解
2019/07/22 Python
Django CSRF跨站请求伪造防护过程解析
2019/07/31 Python
基于Django框架的权限组件rbac实例讲解
2019/08/31 Python
Python判断字符串是否为空和null方法实例
2020/04/26 Python
如何用css3实现switch组件开关的方法
2018/02/09 HTML / CSS
护士个人简历自荐信
2013/10/18 职场文书
大学毕业后的十年规划
2014/01/07 职场文书
安全标准化汇报材料
2014/02/03 职场文书
元旦文艺汇演主持词
2014/03/26 职场文书
三关爱志愿服务活动方案
2014/08/17 职场文书
2015年幼儿园个人工作总结
2015/04/25 职场文书
写作之关于描写老人的好段摘抄
2019/11/14 职场文书
JS一分钟在github+Jekyll的博客中添加访问量功能的实现
2021/04/03 Javascript