您好, 欢迎来到 !    登录 | 注册 | | 设为首页 | 收藏本站

Python 装饰器使用详解

5b51 2022/1/14 8:16:44 python 字数 18195 阅读 334 来源 www.jb51.cc/python

装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象.

概述

装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象.

  经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。

  先来看一个简单例子:

def Now():
  print('2017_7_29')

现在有一个新的需求,希望可以记录下函数的执行日志,于是在代码添加日志代码

def Now():
  print('2017_7_29')
  logging.warn("running")

假设有类似的多个需求,怎么做?再写一个logging在Now函数里?这样就造成大量雷同的代码,为了减少重复写代码,我们可以这样做,重新定义一个函数:专门处理日志 ,日志处理完之后再执行真正的业务代码.

def use_logging(func):   
  logging.warn("%s is running" % func.__name__)   
  func() 

def Now():   
  print('2017_7_29') 
  
use_logging(Now)

在实现,逻辑上不难, 但是这样的话,我们每次都要将一个函数作为参数传递给日志函数。而且这种方式已经破坏了原有的代码逻辑结构,之前执行业务逻辑时,执行运行Now(),但是现在不得不改成use_logging(Now)。那么有没有更好的方式的呢?当然有,答案就是装饰器。

  首先要明白函数也是一个对象,而且函数对象可以被赋值给变量,所以,通过变量也能调用函数。例如:

def Now():
  print('2017_7_28')

f=Now
f()
# 函数对象有一个__name__属性,可以拿到函数的名字
print('Now.__name__:',Now.__name__)
print('f.__name__:',f.__name__)

简单装饰器

本质上,decorator就是一个返回函数的高阶函数。所以,我们要定义一个能打印日志的decorator,可以定义如下:

def log(func):
  def wrapper(*args,**kw):
    print('call %s():'%func.__name__)
    return func(*args,**kw)
  return wrapper

# 由于log()是一个decorator,返回一个函数,所以,原来的Now()函数仍然存在,
# 只是现在同名的Now变量指向了新的函数,于是调用Now()将执行新函数,即在log()函数中返回的wrapper()函数
# wrapper()函数的参数定义是(*args,**kw),因此,wrapper()函数可以接受任意参数的调用
# 在wrapper()函数内,首先打印日志,再紧接着调用原始函数

上面的log,因为它是一个decorator,所以接受一个函数作为参数,并返回一个函数.现在执行:

Now = log(Now)
Now()

输出结果:
    call Now():
    2017_7_28

函数log就是装饰器,它把执行真正业务方法的func包裹在函数里面,看起来像Now被log装饰了。在这个例子中,函数进入时 ,被称为一个横切面(Aspect),这种编程方式被称为面向切面的编程(Aspect-Oriented Programming)。

使用语法糖:

@log
def Now():
  print('2017_7_28')

@符号是装饰器的语法糖,在定义函数的时候使用,避免再一次赋值操作

  这样我们就可以省去Now = log(Now)这一句了,直接调用Now()即可得到想要的结果。如果我们有其他的类似函数,我们可以继续调用装饰器来修饰函数,而不用重复修改函数或者增加新的封装。这样,我们就提高了程序的可重复利用性,并增加了程序的可读性。

  装饰器在Python使用如此方便都要归因于Python的函数能像普通的对象一样能作为参数传递给其他函数,可以被赋值给其他变量,可以作为返回值,可以被定义在另外一个函数内。

  带参数的装饰器:

  如果decorator本身需要传入参数,那就需要编写一个返回decorator的高阶函数,写出来会复杂一点。比如,要自定义log的文本:

def log(text):
  def decorator(func):
    def wrapper(*args,**kw):
      print('%s %s()'%(text,func.__name__))
      return func(*args,**kw)
    return wrapper
  return decorator

这个3层嵌套的decorator用法如下:

@log('goal')
def Now():
  print('2017-7-28')
Now()

等价于

Now = log('goal')(Now)

# 首先执行log('execute'),返回的是decorator函数,再调用返回函数,参数是Now函数,返回值最终是wrapper函数
Now()

因为我们讲了函数也是对象,它有__name__属性,但你去看经过decorator装饰之后的函数,它们的__name__已经从原来的'Now'变成了'wrapper':

print(Now.__name__)
# wrapper

因为返回的那个wrapper()函数名字就是'wrapper',所以,需要把原始函数__name__属性复制到wrapper()函数中,否则,有些依赖函数签名的代码执行就会出错。

不需要编写wrapper.__name__ = func.__name__这样的代码,Python内置的functools.wraps就是干这个事的,所以,一个完整的decorator的写法如下:

import functools

def log(func):
  @functools.wraps(func)
  def wrapper(*args,**kw):
    print('call %s():' % func.__name__)
    return func(*args,**kw)
  return wrapper

import functools

def log(text):
  def decorator(func):
    @functools.wraps(func)
    def wrapper(*args,**kw):
      print('%s %s():' % (text,**kw)
    return wrapper
  return decorator

类装饰器:

  再来看看类装饰器,相比函数装饰器,类装饰器具有灵活度大、高内聚、封装性等优点。使用类装饰器还可以依靠类内部的__call__方法,当使用 @ 形式将装饰器附加到函数上时,就会调用方法

import time

class Foo(object):   
  def __init__(self,func):   
    self._func = func 
  
  def __call__(self):   
    print ('class decorator runing')   
    self._func()   
    print ('class decorator ending') 

@Foo 
def Now():   
  print (time.strftime('%Y-%m-%d',time.localtime(time.time()))) 
  
Now()

总结:

  概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。  

  同时在面向对象(OOP)的设计模式中,decorator被称为装饰模式。OOP的装饰模式需要通过继承和组合来实现,而Python除了能支持OOP的decorator外,直接从语法层次支持decorator。Python的decorator可以用函数实现,也可以用类实现。

总结

以上是编程之家为你收集整理的Python 装饰器使用详解全部内容,希望文章能够帮你解决Python 装饰器使用详解所遇到的程序开发问题。


如果您也喜欢它,动动您的小指点个赞吧

除非注明,文章均由 laddyq.com 整理发布,欢迎转载。

转载请注明:
链接:http://laddyq.com
来源:laddyq.com
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。


联系我
置顶