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

原文地址
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装饰器的资料请关注我们其它相关文章!

(0)

相关推荐

  • python GUI库图形界面开发之PyQt5信号与槽的高级使用技巧装饰器信号与槽详细使用方法与实例

    装饰器信号与槽 所谓装饰器信号与槽,就是通过装饰器的方法来定义信号与槽函数,具体的使用方法如下 @PyQt5.QtCore.pyqtSlot(参数) def on_发送者对象名称_发射信号名称(self,参数): pass 这种方法有效的前提是下面的函数已经执行: QMetaObject.connectSlotsByName(QObject) 在上面的代码中,'发送者对象名称'就是使用setObjectName函数设置的名称,因此自定义槽函数的命名规则也可以看做:on+使用setObjectNa

  • Python装饰器的函数式编程详解

    Python的装饰器的英文名叫Decorator,当你看到这个英文名的时候,你可能会把其跟Design Pattern里的Decorator搞混了,其实这是完全不同的两个东西.虽然好像,他们要干的事都很相似--都是想要对一个已有的模块做一些"修饰工作",所谓修饰工作就是想给现有的模块加上一些小装饰(一些小功能,这些小功能可能好多模块都会用到),但又不让这个小装饰(小功能)侵入到原有的模块中的代码里去.但是OO的Decorator简直就是一场恶梦,不信你就去看看wikipedia上的词条

  • Python如何解除一个装饰器

    问题 一个装饰器已经作用在一个函数上,你想撤销它,直接访问原始的未包装的那个函数. 解决方案 假设装饰器是通过 @wraps 来实现的,那么你可以通过访问 __wrapped__ 属性来访问原始函数: >>> @somedecorator >>> def add(x, y): ... return x + y ... >>> orig_add = add.__wrapped__ >>> orig_add(3, 4) 7 >>

  • Python如何创建装饰器时保留函数元信息

    问题 你写了一个装饰器作用在某个函数上,但是这个函数的重要的元信息比如名字.文档字符串.注解和参数签名都丢失了. 解决方案 任何时候你定义装饰器的时候,都应该使用 functools 库中的 @wraps 装饰器来注解底层包装函数.例如: import time from functools import wraps def timethis(func): ''' Decorator that reports the execution time. ''' @wraps(func) def wr

  • Python中装饰器高级用法详解

    在Python中,装饰器一般用来修饰函数,实现公共功能,达到代码复用的目的.在函数定义前加上@xxxx,然后函数就注入了某些行为,很神奇!然而,这只是语法糖而已. 场景 假设,有一些工作函数,用来对数据做不同的处理: def work_bar(data): pass def work_foo(data): pass 我们想在函数调用前/后输出日志,怎么办? 傻瓜解法 logging.info('begin call work_bar') work_bar(1) logging.info('cal

  • Python中的装饰器用法详解

    本文实例讲述了Python中的装饰器用法.分享给大家供大家参考.具体分析如下: 这里还是先由stackoverflow上面的一个问题引起吧,如果使用如下的代码: 复制代码 代码如下: @makebold @makeitalic def say():    return "Hello" 打印出如下的输出: <b><i>Hello<i></b> 你会怎么做?最后给出的答案是: 复制代码 代码如下: def makebold(fn):    

  • 介绍Python的@property装饰器的用法

    在绑定属性时,如果我们直接把属性暴露出去,虽然写起来很简单,但是,没办法检查参数,导致可以把成绩随便改: s = Student() s.score = 9999 这显然不合逻辑.为了限制score的范围,可以通过一个set_score()方法来设置成绩,再通过一个get_score()来获取成绩,这样,在set_score()方法里,就可以检查参数: class Student(object): def get_score(self): return self._score def set_s

  • python 一篇文章搞懂装饰器所有用法(建议收藏)

    01. 装饰器语法糖 如果你接触 Python 有一段时间了的话,想必你对 @ 符号一定不陌生了,没错 @ 符号就是装饰器的语法糖. 它放在一个函数开始定义的地方,它就像一顶帽子一样戴在这个函数的头上.和这个函数绑定在一起.在我们调用这个函数的时候,第一件事并不是执行这个函数,而是将这个函数做为参数传入它头顶上这顶帽子,这顶帽子我们称之为装饰函数 或 装饰器. 你要问我装饰器可以实现什么功能?我只能说你的脑洞有多大,装饰器就有多强大. 装饰器的使用方法很固定: 先定义一个装饰函数(帽子)(也可以

  • Python @property装饰器原理解析

    这篇文章主要介绍了Python @property装饰器原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 1.通过@property装饰器,可以直接通过方法名来访问方法,不需要在方法名后添加一对"()"小括号. class Person: def __init__(self, name): self.__name = name @property def say(self): return self.__name xioabai

  • Python类中的装饰器在当前类中的声明与调用详解

    我的Python环境:3.7 在Python类里声明一个装饰器,并在这个类里调用这个装饰器. 代码如下: class Test(): xx = False def __init__(self): pass def test(func): def wrapper(self, *args, **kwargs): print(self.xx) return func(self, *args, **kwargs) return wrapper @test def test_a(self,a,b): pr

随机推荐