Python 装饰器使用详解
作者:奋勇前行 发布时间:2021-09-02 05:41:01
装饰器本质上是一个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


猜你喜欢
- '''数据集:伪造数据集(两个高斯分布混合)数据集长度:1000--------------------------
- 我们可用下面的代码将服务器端变量转换为客户端的JavaScrit变量:<%@ Language=VBScript
- 两张表,A表中的记录B表中不一定有。左连接:关注左边,右边没有就为空。右连接:关注右边,左边没有就为空。内连接:返回交集例如:student
- 我们通过单体模式理解了是以对象字面量的方式来创建单体模式的;比如如下的对象字面量的方式代码如下:var singleMode = { &nb
- 字典概述字典是一个映射集合,他储存的是键值对,通过键来查找值,而不是索引字典定义通过大括号{}与键值对来表示一个字典 字典名=
- 背景基于现在微服务或者服务化的思想,我们大部分的业务逻辑处理函数都是长这样的:比如grpc服务端:func (s *Service) Get
- 什么是浮动?浮动是 css 的定位属性。我们可以看一下印刷设计来了解它的起源和作用。印刷布局中,文本可以按照需要围绕图片。一般把这种方式称为
- Entry(输入框)组件通常用于获取用户的输入文本。何时使用 Entry 组件?Entry 组件仅允许用于输入一行文本,如果用于输入的字符串
- eval() 函数用来执行一个字符串表达式,并返回表达式的值。eval函数功能:将字符串str当成有效的表达式来求值并返回计算结果。eval
- 在C++11和C#中都有匿名函数的存在。下面看看在python中匿名函数的使用。1.lambda只是一个表达式,函数体比def简单很多。2.
- 使用python爬虫其实就是方便,它会有各种工具类供你来使用,很方便。Java不可以吗?也可以,使用httpclient工具、还有一个大神写
- 一、matplotlib.pyplot.boxplot()语法boxplot(x, notch=None, sym=None, vert=N
- 上篇文章给大家介绍过解决Navicat for Mysql连接报错1251的问题(连接失败),今天介绍Navicat for MySQL 连
- 1.安装step1step 2 首先是更改安装地址,推荐安装在D盘,如果和我一样C盘容量大的话,也可以不改,直接点击【Next】😄step3
- 利用Object.defineProperty进行数据劫持代码如下<!DOCTYPE html><html lang=&q
- 分享炫酷的前端页面随机二维码验证,供大家参考,具体内容如下直接上代码<%@ page contentType="text/h
- 本文实例讲述了jQuery自适应轮播图插件Swiper用法。分享给大家供大家参考,具体如下:运行效果截图如下:示例代码如下:<!DOC
- 本文实例讲述了python网络编程之文件下载实现方法。分享给大家供大家参考。具体如下:真是越看越喜欢python啊,想要了解它提供的http
- 前言:问题分析:在进行数据库查询的时候,我们都知道索引可以加快数据查询的效率。但是在实际的业务场景下,经常会遇到即使在表中增加了索引,但是同
- 本文实例讲述了javascript常见数字进制转换的方法。分享给大家供大家参考,具体如下:基本思路是先把其他进制的转化成 十进制,然后再转化