深入了解和应用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学习笔记整理3之输入输出、python eval函数
Dec 14 Python
编写Python爬虫抓取豆瓣电影TOP100及用户头像的方法
Jan 20 Python
用yum安装MySQLdb模块的步骤方法
Dec 15 Python
python实现redis三种cas事务操作
Dec 19 Python
Python爬虫之网页图片抓取的方法
Jul 16 Python
使用Python如何测试InnoDB与MyISAM的读写性能
Sep 18 Python
python对文件目录的操作方法实例总结
Jun 24 Python
python tkinter组件使用详解
Sep 16 Python
Pycharm中Python环境配置常见问题解析
Jan 16 Python
tensorflow 获取checkpoint中的变量列表实例
Feb 11 Python
解决pyecharts运行后产生的html文件用浏览器打开空白
Mar 11 Python
python性能测试工具locust的使用
Dec 28 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,ajax实现分页
2008/03/27 PHP
详解laravel passport OAuth2.0的4种模式
2019/11/04 PHP
javascript 面向对象编程基础:继承
2009/08/21 Javascript
在IE 浏览器中使用 jquery的fadeIn() 效果 英文字符字体加粗
2011/06/02 Javascript
js模仿jquery的写法示例代码
2013/06/16 Javascript
JS命名空间的另一种实现
2013/08/09 Javascript
jquery实现图片灯箱明暗的遮罩效果
2013/11/15 Javascript
Nodejs为什么选择javascript为载体语言
2015/01/13 NodeJs
javascript 分号总结及详细介绍
2016/09/24 Javascript
微信小程序中单位rpx和rem的使用
2016/12/06 Javascript
基于Vue实现timepicker
2017/04/25 Javascript
详解vue-router 2.0 常用基础知识点之router.push()
2017/05/10 Javascript
JavaScript中使用Async实现异步控制
2017/08/15 Javascript
五步轻松实现zTree的使用
2017/11/01 Javascript
关于jquery layui弹出层的使用方法
2018/04/21 jQuery
uni-app自定义导航栏按钮|uniapp仿微信顶部导航条功能
2019/11/12 Javascript
vue项目使用高德地图的定位及关键字搜索功能的实例代码(踩坑经验)
2020/03/07 Javascript
vue使用vant中的checkbox实现全选功能
2020/11/17 Vue.js
利用Vue实现简易播放器的完整代码
2020/12/30 Vue.js
[01:22:19]EG vs TNC Supermajor小组赛B组败者组第一轮 BO3 第二场 6.2
2018/06/03 DOTA
Python发送form-data请求及拼接form-data内容的方法
2016/03/05 Python
如何使用VSCode愉快的写Python于调试配置步骤
2018/04/06 Python
Appium+python自动化怎么查看程序所占端口号和IP
2019/06/14 Python
python选取特定列 pandas iloc,loc,icol的使用详解(列切片及行切片)
2019/08/06 Python
使用Html5实现异步上传文件,支持跨域,带有上传进度条
2016/09/17 HTML / CSS
静态变量和实例变量的区别
2015/07/07 面试题
《临死前的严监生》教学反思
2014/02/13 职场文书
士力架广告词
2014/03/20 职场文书
自主招生推荐信范文
2014/05/10 职场文书
董事长秘书工作职责
2014/06/10 职场文书
舞蹈教育学专业求职信
2014/06/29 职场文书
材料物理专业求职信
2014/09/01 职场文书
中级会计大学生职业生涯规划书
2014/09/16 职场文书
批评与自我批评范文
2014/10/15 职场文书
德能勤绩工作总结
2015/08/11 职场文书
PHP实现创建以太坊钱包转账等功能
2021/04/21 PHP