网络编程
位置:首页>> 网络编程>> Python编程>> Python 装饰器使用详解

Python 装饰器使用详解

作者:奋勇前行  发布时间:2021-09-02 05:41:01 

标签: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, func.__name__))
     return func(*args, **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可以用函数实现,也可以用类实现。

来源:http://www.cnblogs.com/fyqx/p/7254046.html

0
投稿

猜你喜欢

  • qqbot 是一个用 python 实现的、基于腾讯 SmartQQ 协议的 QQ 机器人框架,可运行在 Linux 、 Windows 和
  • 背景(background)在项目中经常会使用。这篇文章主要讲解的是实际项目中的5个实例。通过具体的分析来达到学习的目的。1,Li列表通过u
  • 今天来和大家聊聊抽样的几种常用方法,以及在Python中是如何实现的。抽样是统计学、机器学习中非常重要,也是经常用到的方法,因为大多时候使用
  • 有很多对于PHP的抱怨,甚至这些抱怨也出自很多聪明的人。当Jeff Atwood写下对于PHP的另一篇抱怨文章之后,我思考了下PHP的好的方
  • Oracle text是Oracle的全文检索技术,是9i版本标准版和企业版的一部分。Oracle text使用标准的sql语言索引、查找、
  • Access爱好者以会VBa为荣。我觉得这不是好现象。vba只是vb的子集,有着很多限制,比如不支持继承,不支持指针,不支持子界类型等。使用
  • 前言:Python主要有三种数据类型:字典、列表、元组。其分别由花括号,中括号,小括号表示。如:字典:dic={'a':12
  • 前言上篇文章给大家带来了PHP中最基本的特性,不知道大家学习的怎样了,回顾上文,我们讲了MD5强弱碰撞以及正则匹配的绕过,总体来看还是很简单
  • 1、引言选择排序里面主要讲了三个排序,分别是简单选择排序、树形选择排序、堆排序。今天这篇文章主要讲树形选择排序,树形选择排序也被称为锦标赛排
  • 注:因为最近想用一下Python做一些简单小游戏的开发作为项目练手之用,而Pygame模块里面提供了大量的有用的方法和属性。今天我们就在之前
  • 【原文地址】Tip/Trick: Url Rewriting with ASP.NET 【原文发表日期】 Monday, February
  • 一、绘图命令操纵海龟绘图有很多命令,可以划分为三种:画笔运动命令、画笔控制命令、全局控制命令1、画笔运动命令命令说明turtle.forwa
  • 最近项目很忙没机会更新博客。有朋友在和我谈学艺术的就业问题,就随便谈一下自己的想法。每年中国美院报考的人数与日俱增,越来越多的人投入到艺术设
  • 什么是模板匹配模板匹配是指在当前图像A内寻找与图像B最相似的部分,可以理解找茬,但是这里是找出一样的信息。一般我们将图像A称为输入图像,将图
  • 1 任务需求  首先,我们来明确一下本文所需实现的需求。  现有一个
  • css可以处理16,777,216颜色,可以使用名字、rgb值或十六进制代码。red红色等同于 rgb(255,0,0) &nbs
  • 表一、运算符与特殊字符 运算符描述/选择子元素,返回左侧元素的直接子元素;如果"/"位于最左侧表示选择根结点的直接子元素
  • 本文实例讲述了php简单实现批量上传图片的方法。分享给大家供大家参考,具体如下:<?phpfunction upload_multi(
  • i前端:nput_test.html<!DOCTYPE html><html><head lang="
  • 昨天同事无意又谈起了这个老话题,美工和设计师(视觉)有什么不同?以文字排版设计为例,列了下面两个图来说明,可能会有一些启发, 第一个图应该算
手机版 网络编程 asp之家 www.aspxhome.com