深入了解和应用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调用新浪微博API项目实践
Jul 28 Python
tensorflow实现softma识别MNIST
Mar 12 Python
Python加载带有注释的Json文件实例
May 23 Python
Python设计模式之组合模式原理与用法实例分析
Jan 11 Python
浅析Python 读取图像文件的性能对比
Mar 07 Python
softmax及python实现过程解析
Sep 30 Python
python3.6使用SMTP协议发送邮件
May 20 Python
pandas数据处理之绘图的实现
Jun 15 Python
python 爬取英雄联盟皮肤并下载的示例
Dec 04 Python
利用python绘制正态分布曲线
Jan 04 Python
python 可视化库PyG2Plot的使用
Jan 21 Python
Pandas-DataFrame知识点汇总
Mar 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文件向另一个地址post数据,不用表单和隐藏的变量的
2007/03/06 PHP
PHP5.2中date()函数显示时间与北京时间相差8小时的解决办法
2009/05/28 PHP
php 论坛采集程序 模拟登陆,抓取页面 实现代码
2009/07/09 PHP
浅谈PHP 闭包特性在实际应用中的问题
2009/10/30 PHP
PHP高自定义性安全验证码代码
2011/11/27 PHP
php中header设置常见文件类型的content-type
2015/06/23 PHP
PHP面向对象五大原则之单一职责原则(SRP)详解
2018/04/04 PHP
jQuery插件jQuery-JSONP开发ajax调用使用注意事项
2013/11/22 Javascript
Jquery模仿Baidu、Google搜索时自动补充搜索结果提示
2013/12/26 Javascript
jquery.form.js实现将form提交转为ajax方式提交的方法
2015/04/07 Javascript
介绍JavaScript的一个微型模版
2015/06/24 Javascript
Angularjs 制作购物车功能实例代码
2016/09/14 Javascript
vuejs2.0实现分页组件使用$emit进行事件监听数据传递的方法
2017/02/22 Javascript
理解Angular的providers给Http添加默认headers
2017/07/04 Javascript
layui点击左侧导航栏,实现不刷新整个页面,只刷新局部的方法
2019/09/25 Javascript
swiper4实现移动端导航栏tab滑动切换
2020/10/16 Javascript
[03:02]安得倚天剑,跨海斩长鲸——中国军团出征DOTA2国际邀请赛
2018/08/14 DOTA
简述Python中的面向对象编程的概念
2015/04/27 Python
Python监控主机是否存活并以邮件报警
2015/09/22 Python
Python实现PS滤镜的旋转模糊功能示例
2018/01/20 Python
Python之pandas读写文件乱码的解决方法
2018/04/20 Python
python使用PIL给图片添加文字生成海报示例
2018/08/17 Python
Empty test suite.(PyCharm程序运行错误的解决方法)
2018/11/30 Python
解决django前后端分离csrf验证的问题
2019/02/03 Python
python3文件复制、延迟文件复制任务的实现方法
2019/09/02 Python
新年福利来一波之Python轻松集齐五福(demo)
2020/01/20 Python
Python如何使用OS模块调用cmd
2020/02/27 Python
Python读取Excel一列并计算所有对象出现次数的方法
2020/09/04 Python
CSS3 3D立方体效果示例-transform也不过如此
2016/12/05 HTML / CSS
美国高档百货Nordstrom的折扣店:Nordstrom Rack
2017/11/13 全球购物
后勤人员自我鉴定
2013/10/20 职场文书
毕业生个人投资创业计划书
2014/01/04 职场文书
财产公证书样本
2014/04/04 职场文书
2014年幼儿园教学工作总结
2014/12/04 职场文书
php 防护xss,PHP的防御XSS注入的终极解决方案
2021/04/01 PHP
MySQL RC事务隔离的实现
2022/03/31 MySQL