深入了解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 相关文章推荐
详解Swift中属性的声明与作用
Jun 30 Python
Python对文件和目录进行操作的方法(file对象/os/os.path/shutil 模块)
May 08 Python
python numpy函数中的linspace创建等差数列详解
Oct 13 Python
Python列表推导式与生成器表达式用法示例
Feb 08 Python
Pandas 对Dataframe结构排序的实现方法
Apr 10 Python
详解Python3.6的py文件打包生成exe
Jul 13 Python
Python解决pip install时出现的Could not fetch URL问题
Aug 01 Python
基于Python 中函数的 收集参数 机制
Dec 21 Python
python时间与Unix时间戳相互转换方法详解
Feb 13 Python
jupyter notebook 增加kernel教程
Apr 10 Python
Python文件操作模拟用户登陆代码实例
Jun 09 Python
python爬虫beautifulsoup解析html方法
Dec 07 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
德劲1102收音机的打理维修案例
2021/03/02 无线电
PHP is_array() 检测变量是否是数组的实现方法
2016/06/13 PHP
JQuery自定义事件的应用 JQuery最佳实践
2010/08/01 Javascript
JavaScript自定义DateDiff函数(兼容所有浏览器)
2012/03/01 Javascript
教你使用javascript简单写一个页面模板引擎
2015/05/05 Javascript
利用JavaScript的AngularJS库制作电子名片的方法
2015/06/18 Javascript
javascript获取重复次数最多的字符
2015/07/08 Javascript
javascript 数组去重复(在线去重工具)
2016/12/17 Javascript
关于react中组件通信的几种方式详解
2017/12/10 Javascript
Angular自定义组件实现数据双向数据绑定的实例
2017/12/11 Javascript
ES6使用 Array.includes 处理多重条件用法实例分析
2020/03/02 Javascript
React倒计时功能实现代码——解耦通用
2020/09/18 Javascript
[02:03]《现实生活中的DOTA2》—林书豪&DOTA2职业选手出演短片
2015/08/18 DOTA
Python可跨平台实现获取按键的方法
2015/03/05 Python
简单介绍Python的轻便web框架Bottle
2015/04/08 Python
Python中max函数用法实例分析
2015/07/17 Python
Python更新数据库脚本两种方法及对比介绍
2017/07/27 Python
python使用logging模块发送邮件代码示例
2018/01/18 Python
Python中数组,列表:冒号的灵活用法介绍(np数组,列表倒序)
2018/04/18 Python
PyQt5实现下载进度条效果
2018/04/19 Python
详解Python3中setuptools、Pip安装教程
2019/06/18 Python
python 实现目录复制的三种小结
2019/12/04 Python
Python log模块logging记录打印用法解析
2020/01/20 Python
Django+Celery实现动态配置定时任务的方法示例
2020/05/26 Python
Python实现中英文全文搜索的示例
2020/12/04 Python
PyTorch中clone()、detach()及相关扩展详解
2020/12/09 Python
python爬虫scrapy基于CrawlSpider类的全站数据爬取示例解析
2021/02/20 Python
详解Canvas 实现炫丽的粒子运动效果(粒子生成文字)
2018/02/01 HTML / CSS
汽车工程专业应届生求职信
2013/10/19 职场文书
高中学生期末评语
2014/04/25 职场文书
计算机网络专业求职信
2014/06/05 职场文书
2014年禁毒工作总结
2014/11/24 职场文书
道德与公民自我评价
2015/03/09 职场文书
2015年出纳个人工作总结
2015/04/02 职场文书
志愿服务心得体会
2016/01/15 职场文书
SQL SERVER实现连接与合并查询
2022/02/24 SQL Server