深入了解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的迭代器与生成器实例详解
Jul 16 Python
Python使用tablib生成excel文件的简单实现方法
Mar 16 Python
python基础之入门必看操作
Jul 26 Python
python pandas中DataFrame类型数据操作函数的方法
Apr 08 Python
Python面向对象程序设计之继承与多继承用法分析
Jul 13 Python
python实现简单flappy bird
Dec 24 Python
python实现超市管理系统(后台管理)
Oct 25 Python
python 函数中的参数类型
Feb 11 Python
pycharm中导入模块错误时提示Try to run this command from the system terminal
Mar 26 Python
Python的Django框架实现数据库查询(不返回QuerySet的方法)
May 19 Python
Python调用OpenCV实现图像平滑代码实例
Jun 19 Python
python四个坐标点对图片区域最小外接矩形进行裁剪
Jun 04 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
《DOTA3》开发工作已经开始 《DOTA3》将代替《DOTA2》
2021/03/06 DOTA
PHP与MySQL开发中页面乱码的产生与解决
2008/03/27 PHP
PHP中查询SQL Server或Sybase时TEXT字段被截断的解决方法
2009/03/10 PHP
thinkphp控制器调度使用示例
2014/02/24 PHP
Zend Framework基本页面布局分析
2016/03/19 PHP
php使用redis的有序集合zset实现延迟队列应用示例
2020/02/20 PHP
JS Excel读取和写入操作(模板操作)实现代码
2010/04/11 Javascript
JQuery操作单选按钮以及复选按钮示例
2013/09/23 Javascript
JS控制输入框内字符串长度
2014/05/21 Javascript
javascript对中文按照拼音排序代码
2014/08/20 Javascript
JQuery通过AJAX从后台获取信息显示在表格上并支持行选中
2015/09/15 Javascript
JQuery.Ajax()的data参数类型实例详解
2015/11/20 Javascript
javascript瀑布流布局实现方法详解
2016/02/17 Javascript
Angular发布1.5正式版,专注于向Angular 2的过渡
2016/02/18 Javascript
js实现随机抽选效果、随机抽选红色球效果
2017/01/13 Javascript
JS表单提交验证、input(type=number) 去三角 刷新验证码
2017/06/21 Javascript
bootstrap Table服务端处理分页(后台是.net)
2017/10/19 Javascript
layui实现根据table数据判断按钮显示情况的方法
2019/09/26 Javascript
关于layui 实现点击按钮添加一行(方法渲染创建的table)
2019/09/29 Javascript
解决vue-router 嵌套路由没反应的问题
2020/09/22 Javascript
Python中音频处理库pydub的使用教程
2017/06/07 Python
tensorflow实现softma识别MNIST
2018/03/12 Python
使用Python向C语言的链接库传递数组、结构体、指针类型的数据
2019/01/29 Python
Python脚本按照当前日期创建多级目录
2019/03/01 Python
python爬虫 线程池创建并获取文件代码实例
2019/09/28 Python
Python实现二叉树的最小深度的两种方法
2019/09/30 Python
python实现树的深度优先遍历与广度优先遍历详解
2019/10/26 Python
python中有关时间日期格式转换问题
2019/12/25 Python
深入浅析CSS3中的Flex布局整理
2020/04/27 HTML / CSS
Sasa莎莎海外旗舰店:香港莎莎美妆平台
2018/03/21 全球购物
留学推荐信(中英文版)
2015/03/26 职场文书
师德承诺书2015
2015/04/28 职场文书
2015重阳节敬老活动总结
2015/07/29 职场文书
党员公开承诺书2016
2016/03/24 职场文书
Golang生成Excel文档的方法步骤
2021/06/09 Golang
Python pandas读取CSV文件的注意事项(适合新手)
2021/06/20 Python