深入了解和应用Python 装饰器 @decorator


Posted in Python onApril 02, 2019

深入了解和应用Python 装饰器 @decorator

Python的装饰器(decorator)是一个很棒的机制,也是熟练运用Python的必杀技之一。装饰器,顾名思义,就是用来装饰的,它装饰的是一个函数,保持被装饰函数的原有功能,再装饰上(添油加醋)一些其它功能,并返回带有新增功能的函数对象,所以装饰器本质上是一个返回函数对象的函数(确切的说,装饰器应该是可调用对象,除了函数,类也可以作为装饰器)。

在编程过程中,我们经常遇到这样的场景:登录校验,权限校验,日志记录等,这些功能代码在各个环节都可能需要,但又十分雷同,通过装饰器来抽象、剥离这部分代码可以很好解决这类场景。

装饰器是什么?

要理解Python的装饰器,首先我们先理解一下Python的函数对象。我们知道,在Python里一切都是对象,函数也不例外,函数是第一类对象(first-class objects),它可以赋值给变量,也可以作为list的元素,还可以作为参数传递给其它函数。

函数可以被变量引用

定义一个简单的函数:

def say_hi():
 print('Hi!')
say_hi()
# Output: Hi!

个变量say_hi2来引用say_hi函数:

say_hi2 = say_hi
print(say_hi2)
# Output: <function say_hi at 0x7fed671c4378>

say_hi2()
# Output: Hi!

上面的语句中say_hi2 和 say_hi 指向了同样的函数定义,二者的执行结果也相同。

函数可以作为参数传递给其它函数

def say_more(say_hi_func):
 print('More')
 say_hi_func()

say_more(say_hi)
# Output:
# More
# Hi

在上面的例子中,我们把say_hi函数当做参数传递给say_more函数,say_hi 被变量 say_hi_func 引用。

函数可以定义在其它函数内部

def say_hi():
 print('Hi!')
 def say_name():
 print('Tom')
 say_name()

say_hi()
# Output:
# Hi!
# Tom

say_name() # 报错

上述代码中,我们在say_hi()函数内部定义了另外一个函数say_name()。say_name()只在say_hi函数内部可见(即,它的作用域在say_hi函数内部),在say_hi外包调用时就会出错。

函数可以返回其它函数的引用

def say_hi():
 print('Hi!')
 def say_name():
 print('Tom')
 return say_name

say_name_func = say_hi()
# 打印Hi!,并返回say_name函数对象
# 并赋值给say_name_func

say_name_func()
# 打印 Tom

上面的例子,say_hi函数返回了其内部定义的函数say_name的引用。这样在say_hi函数外部也可以使用say_name函数了。

前面我们理解了函数,这有助于我们接下来弄明白装饰器。

装饰器(Decorator)

装饰器是可调用对象(callable objects),它用来修改函数或类。
可调用对象就是可以接受某些参数并返回某些对象的对象。Python里的函数和类都是可调用对象。

函数装饰器,就是接受函数作为参数,并对函数参数做一些包装,然后返回增加了包装的函数,即生成了一个新函数。

让我们看看下面这个例子:

def decorator_func(some_func):
 # define another wrapper function which modifies some_func
 def wrapper_func():
 print("Wrapper function started")
 
 some_func()
 
 print("Wrapper function ended")
 
 return wrapper_func # Wrapper function add something to the passed function and decorator returns the wrapper function
 
def say_hello():
 print ("Hello")
 
say_hello = decorator_func(say_hello)

say_hello()

# Output:
# Wrapper function started
# Hello
# Wrapper function ended

上面例子中,decorator_func 就是定义的装饰器函数,它接受some_func作为参数。它定义了一个wrapper_func函数,该函数调用了some_func但也增加了一些自己的代码。

上面代码中使用装饰器的方法看起来有点复杂,其实真正的装饰器的Python语法是这样的:

装饰器的Python语法

@decorator_func
def say_hi():
 print 'Hi!'

@ 符合是装饰器的语法糖,在定义函数say_hi时使用,避免了再一次的赋值语句。
上面的语句等同于:

def say_hi():
 print 'Hi!'
say_hi = decorator_func(say_hi)

装饰器的顺序

@a
@b
@c
def foo():
 print('foo')

# 等同于:
foo = a(b(c(foo)))

带参数函数的装饰器

def decorator_func(some_func):
 def wrapper_func(*args, **kwargs):
 print("Wrapper function started")
 some_func(*args, **kwargs)
 print("Wrapper function ended")
 
 return wrapper_func

@decorator_func 
def say_hi(name):
 print ("Hi!" + name)

上面代码中,say_hi函数带有一个参数。通常情况下,不同功能的函数可以有不同类别、不同数量的参数,在写wrapper_func的时候,我们不确定参数的名称和数量,可以通过*args 和 **kwargs 来引用函数参数。

带参数的装饰器

不仅被装饰的函数可以带参数,装饰器本身也可以带参数。参考下面的例子:

def use_logging(level):
 def decorator(func):
 def wrapper(*args, **kwargs):
 if level == "warn":
 logging.warn("%s is running" % func.__name__)
 return func(*args)
 return wrapper

 return decorator

@use_logging(level="warn")
def foo(name='foo'):
 print("i am %s" % name)

简单来说,带参数的装饰器就是在没有参数的装饰器外面再嵌套一个参数的函数,该函数返回那个无参数装饰器即可。

类作为装饰器

前面我们提到装饰器是可调用对象。在Python里面,除了函数,类也是可调用对象。使用类装饰器,优点是灵活性大,高内聚,封装性。通过实现类内部的__call__方法,当使用 @ 语法糖把装饰器附加到函数上时,就会调用此方法。

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 say_hi():
 print('Hi!')

say_hi()
# Output:
# class decorator running
# Hi!
# class decorator ending

functools.wraps

使用装饰器极大地复用了代码,但是他有一个缺点就是原函数的元信息不见了,比如函数的docstring、__name__、参数列表,先看看下面例子:

def decorator_func(some_func):
 def wrapper_func(*args, **kwargs):
 print("Wrapper function started")
 some_func(*args, **kwargs)
 print("Wrapper function ended")
 
 return wrapper_func

@decorator_func 
def say_hi(name):
 '''Say hi to somebody'''
 print ("Hi!" + name)

print(say_hi.__name__) # Output: wrapper_func
print(say_hi.__doc__) # Output: None

可以看到,say_hi函数被wrapper_func函数取代,它的__name__ 和 docstring 也自然是wrapper_func函数的了。
不过不用担心,Python有functools.wraps,wraps本身也是一个装饰器,它的作用就是把原函数的元信息拷贝到装饰器函数中,使得装饰器函数也有和原函数一样的元信息。

from functools import wraps
def decorator_func(some_func):
 @wraps(func)
 def wrapper_func(*args, **kwargs):
 print("Wrapper function started")
 some_func(*args, **kwargs)
 print("Wrapper function ended")
 
 return wrapper_func

@decorator_func 
def say_hi(name):
 '''Say hi to somebody'''
 print ("Hi!" + name)

print(say_hi.__name__) # Output: say_hi
print(say_hi.__doc__) # Output: Say hi to somebody

类的内置装饰器

  • 类属性@property
  • 静态方法@staticmethod
  • 类方法@classmethod

通常,我们需要先实例化一个类的对象,再调用其方法。

若类的方法使用了@staticmethod或@classmethod,就可以不需要实例化,直接类名.方法名()来调用。

从使用上来看,@staticmethod不需要指代自身对象的self或指代自身类的cls参数,就跟使用普通函数一样。

@classmethod不需要self参数,但第一个参数必须是指代自身类的cls参数。如果在@staticmethod中要调用到这个类的一些属性方法,只能直接类名.属性名,或类名.方法名的方式。

而@classmethod因为持有cls参数,可以来调用类的属性,类的方法,实例化对象等。

总结

通过认识Python的函数,我们逐步弄清了装饰器的来龙去脉。装饰器是代码复用的好工具,在编程过程中可以在适当的场景用多多使用。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Python 相关文章推荐
Python中无限元素列表的实现方法
Aug 18 Python
python 调用HBase的简单实例
Dec 18 Python
Python在不同目录下导入模块的实现方法
Oct 27 Python
Python最火、R极具潜力 2017机器学习调查报告
Dec 11 Python
小白入门篇使用Python搭建点击率预估模型
Oct 12 Python
python 实现将txt文件多行合并为一行并将中间的空格去掉方法
Dec 20 Python
Python OpenCV对本地视频文件进行分帧保存的实例
Jan 08 Python
django框架实现一次性上传多个文件功能示例【批量上传】
Jun 19 Python
Python基于jieba, wordcloud库生成中文词云
May 13 Python
Python常用模块函数代码汇总解析
Aug 31 Python
python实现股票历史数据可视化分析案例
Jun 10 Python
python使用pymysql模块操作MySQL
Jun 16 Python
python使用Plotly绘图工具绘制散点图、线形图
Apr 02 #Python
浅谈python的输入输出,注释,基本数据类型
Apr 02 #Python
windows下numpy下载与安装图文教程
Apr 02 #Python
python环境路径配置以及命令行运行脚本
Apr 02 #Python
详解Python使用Plotly绘图工具,绘制甘特图
Apr 02 #Python
python查询文件夹下excel的sheet名代码实例
Apr 02 #Python
python3.6下Numpy库下载与安装图文教程
Apr 02 #Python
You might like
php daodb插入、更新与删除数据
2009/03/19 PHP
PHP 裁剪图片
2021/03/09 PHP
一个简单的js动画效果代码
2010/07/20 Javascript
五段实用的js高级技巧
2011/12/20 Javascript
基于jquery的滚动条滚动固定div(附演示下载)
2012/10/29 Javascript
JQuery中阻止事件冒泡几种方式及其区别介绍
2014/01/15 Javascript
js鼠标点击图片切换效果代码分享
2015/08/26 Javascript
jQuery实现网页顶部固定导航效果代码
2015/12/24 Javascript
js return返回多个值,通过对象的属性访问方法
2017/02/21 Javascript
vue Render中slots的使用的实例代码
2017/07/19 Javascript
基于Vue单文件组件详解
2017/09/15 Javascript
Nodejs中怎么实现函数的串行执行
2019/03/02 NodeJs
微信小程序一周时间表功能实现
2019/10/17 Javascript
使用vue cli4.x搭建vue项目的过程详解
2020/05/08 Javascript
vue 解决IOS10低版本白屏的问题
2020/11/17 Javascript
Python实现爬取百度贴吧帖子所有楼层图片的爬虫示例
2018/04/26 Python
python dataframe常见操作方法:实现取行、列、切片、统计特征值
2018/06/09 Python
python使用folium库绘制地图点击框
2018/09/21 Python
pygame实现贪吃蛇游戏(下)
2019/10/29 Python
Html5游戏开发之乒乓Ping Pong游戏示例(三)
2013/01/21 HTML / CSS
HTML5地理定位_动力节点Java学院整理
2017/07/12 HTML / CSS
英国第一蛋白粉品牌:Myprotein
2016/09/14 全球购物
爱普生美国官网:Epson美国
2018/11/05 全球购物
萨克斯第五大道英国:Saks Fifth Avenue英国
2019/04/01 全球购物
逻辑链路控制协议
2016/10/01 面试题
写给女生的道歉信
2014/01/14 职场文书
大学生职业生涯规划书范文
2014/01/14 职场文书
行政部工作岗位职责范本
2014/03/05 职场文书
爱国主义演讲稿
2014/05/07 职场文书
工伤私了协议书范本
2014/11/24 职场文书
党支部创先争优公开承诺书
2015/04/30 职场文书
2015年惩防体系建设工作总结
2015/05/22 职场文书
党员转正大会主持词
2015/07/02 职场文书
2016年大学生就业指导课心得体会
2015/10/09 职场文书
假如给我三天光明:舟逆水而行,人遇挫而达 
2019/10/29 职场文书
Oracle表空间与权限的深入讲解
2021/11/17 Oracle