深入了解和应用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正则表达式 re模块使用说明
May 19 Python
python实现bitmap数据结构详解
Feb 17 Python
Python学习笔记(二)基础语法
Jun 06 Python
Python通过DOM和SAX方式解析XML的应用实例分享
Nov 16 Python
Python读取图片为16进制表示简单代码
Jan 19 Python
Python使用functools实现注解同步方法
Feb 06 Python
TensorFlow实现模型评估
Sep 07 Python
Python实现查找二叉搜索树第k大的节点功能示例
Jan 24 Python
python dlib人脸识别代码实例
Apr 04 Python
利用Python库Scapy解析pcap文件的方法
Jul 23 Python
Python中使用threading.Event协调线程的运行详解
May 02 Python
python 批量压缩图片的脚本
Jun 02 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编写的SVN类
2013/07/18 PHP
php字符串的替换,分割和连接方法
2016/05/23 PHP
jQuery 瀑布流 浮动布局(一)(延迟AJAX加载图片)
2012/05/23 Javascript
表单验证的完整应用案例探讨
2013/03/29 Javascript
jquery利用ajax调用后台方法实例
2013/08/23 Javascript
Javascript中的String对象详谈
2014/03/03 Javascript
JavaScript检测鼠标移动方向的方法
2015/05/22 Javascript
基于jQuery日历插件制作日历
2016/03/11 Javascript
AngularJS使用angular.bootstrap完成模块手动加载的方法分析
2017/01/19 Javascript
React Native时间转换格式工具类分享
2017/10/24 Javascript
讲解vue-router之什么是动态路由
2018/05/28 Javascript
jquery中为什么能用$操作
2019/06/18 jQuery
详解javascript中var与ES6规范中let、const区别与用法
2020/01/11 Javascript
微信小程序反编译的实现
2020/12/10 Javascript
js闭包和垃圾回收机制示例详解
2021/03/01 Javascript
[53:10]Secret vs Pain 2018国际邀请赛小组赛BO2 第一场 8.17
2018/08/20 DOTA
Python 的 Socket 编程
2015/03/24 Python
浅析Python多线程下的变量问题
2015/04/28 Python
理解Python中的With语句
2016/03/18 Python
Python 基于Twisted框架的文件夹网络传输源码
2016/08/28 Python
基于Python中capitalize()与title()的区别详解
2017/12/09 Python
局域网内python socket实现windows与linux间的消息传送
2019/04/19 Python
python游戏开发之视频转彩色字符动画
2019/04/26 Python
python中下标和切片的使用方法解析
2019/08/27 Python
python向图片里添加文字
2019/11/26 Python
如何在Python 游戏中模拟引力
2020/03/27 Python
Keras SGD 随机梯度下降优化器参数设置方式
2020/06/19 Python
解决keras使用cov1D函数的输入问题
2020/06/29 Python
css3 border旋转时的动画应用
2016/01/22 HTML / CSS
html5是什么_动力节点Java学院整理
2017/07/07 HTML / CSS
数据库方面面试题
2012/04/22 面试题
中职生自荐信范文
2014/06/15 职场文书
乡镇务虚会发言材料
2014/10/20 职场文书
政风行风整改方案
2014/10/25 职场文书
2015年度销售个人工作总结
2015/03/31 职场文书
2015学校年度工作总结
2015/05/11 职场文书