深入了解和应用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的Django框架使用
Feb 18 Python
Python3实现发送QQ邮件功能(文本)
Dec 15 Python
Python断言assert的用法代码解析
Feb 03 Python
Python反射和内置方法重写操作详解
Aug 27 Python
python使用socket实现的传输demo示例【基于TCP协议】
Sep 24 Python
python生成器推导式用法简单示例
Oct 08 Python
Python爬取365好书中小说代码实例
Feb 28 Python
python模块如何查看
Jun 16 Python
PyTorch: Softmax多分类实战操作
Jul 07 Python
Python+pyftpdlib实现局域网文件互传
Aug 24 Python
深入理解python协程
Jun 15 Python
利用Python实时获取steam特惠游戏数据
Jun 25 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
snoopy 强大的PHP采集类使用实例代码
2010/12/09 PHP
php标签云的实现代码
2012/10/10 PHP
用php制作简单分页(从数据库读取记录)的方法详解
2013/05/04 PHP
thinkphp3.2.2前后台公用类架构问题分析
2014/11/25 PHP
CI框架(CodeIgniter)实现的数据库增删改查操作总结
2018/05/23 PHP
Javascript 定时器调用传递参数的方法
2009/11/12 Javascript
两种方法实现文本框输入内容提示消失
2013/03/17 Javascript
JS 修改URL参数(实现代码)
2013/07/08 Javascript
jquery win 7透明弹出层效果的简单代码
2013/08/06 Javascript
使用jQuery.wechat构建微信WEB应用
2014/10/09 Javascript
js实现jquery的offset()方法实例
2015/01/10 Javascript
30分钟快速掌握Bootstrap框架
2016/05/24 Javascript
关于JavaScript数组你所不知道的3件事
2016/08/24 Javascript
AngularJs bootstrap搭载前台框架——js控制部分
2016/09/01 Javascript
Javascript中this关键字指向问题的测试与详解
2017/08/11 Javascript
Javascript中的getter和setter初识
2017/08/17 Javascript
Vue开发Html5微信公众号的步骤
2019/04/11 Javascript
使用vue-router切换页面时实现设置过渡动画
2019/10/31 Javascript
NodeJS http模块用法示例【创建web服务器/客户端】
2019/11/05 NodeJs
Jquery cookie插件实现原理代码解析
2020/08/04 jQuery
Python selenium 三种等待方式解读
2016/09/15 Python
python3写爬取B站视频弹幕功能
2017/12/22 Python
CentOS6.9 Python环境配置(python2.7、pip、virtualenv)
2019/05/06 Python
如何写python的配置文件
2020/06/07 Python
Python使用Selenium实现淘宝抢单的流程分析
2020/06/23 Python
Python爬虫进阶之爬取某视频并下载的实现
2020/12/08 Python
Html5百叶窗效果的示例代码
2017/12/11 HTML / CSS
Canvas高级路径操作之拖拽对象的实现
2019/08/05 HTML / CSS
Java中实现多态的机制
2015/08/09 面试题
高三自我鉴定怎么写
2013/10/19 职场文书
西游降魔篇观后感
2015/06/15 职场文书
户外拓展训练感想
2015/08/07 职场文书
python制作图形界面的2048游戏, 基于tkinter
2021/04/06 Python
MYSQL数据库使用UTF-8中文编码乱码的解决办法
2021/05/26 MySQL
Win10 heic文件怎么打开 ? Win10 heic文件打开教程
2022/04/06 数码科技
一行Python命令实现批量加水印
2022/04/07 Python