通俗讲解python 装饰器


Posted in Python onSeptember 07, 2020

装饰器其实一直是我的一个"老大难"。这个知识点就放在那,但是拖延症。。。

其实在平常写写脚本的过程中,这个知识点你可能用到不多

但在面试的时候,这可是一个高频问题。

一、什么是装饰器

所谓的装饰器,其实就是通过装饰器函数,来修改原函数的一些功能,使得原函数不需要修改。

这一句话理解起来可能没那么轻松,那先来看一个"傻瓜"函数。

放心,绝对不是"Hello World"!

def hello():
  print("你好,装饰器")

肿么样,木骗你吧? 哈哈,这个函数不用运行相信大家都知道输出结果:"你好,装饰器"。

那如果我想让hello()函数再实现个其他功能,比如多打印一句话。

那么,可以这样"增强"一下:

def my_decorator(func):
  def wrapper():
    print("这是装饰后具有的新输出")
    func()
  return wrapper

def hello():
  print("你好,装饰器")

hello = my_decorator(hello)

hello()

运行结果:

这是装饰后具有的新输出
你好,装饰器
[Finished in 0.1s]

很显然,这个"增强"没啥作用,但是可以帮助理解装饰器。

当运行最后的hello()函数时,调用过程是这样的:

  1. hello = my_decorator(hello)中,变量hello指向的是my_decorator()
  2. my_decorator(func)中传参是hello,返回的wrapper,因此又会调用到原函数hello()
  3. 于是乎,先打印出了wrapper()函数里的,然后才打印出hello()函数里的

那上述代码里的my_decorator()就是一个装饰器。
它改变了hello()的行为,但是并没有去真正的改变hello()函数的内部实现。

但是,python一直以"优雅"被人追捧,而上述的代码显然不够优雅。

二、优雅的装饰器

所以,想让上述装饰器变得优雅,可以这样写:

def my_decorator(func):
  def wrapper():
    print("这是装饰后具有的新输出")
    func()
  return wrapper

@my_decorator
def hello():
  print("你好,装饰器")

hello()

这里的@my_decorator就相当于旧代码的hello = my_decorator(hello),@符号称为语法糖。

那如果还有其他函数也需要加上类似的装饰,直接在函数的上方加上@my_decorator就可以,大大提高函数
的重复利用与可读性。

def my_decorator(func):
  def wrapper():
    print("这是装饰后具有的新输出")
    func()
  return wrapper

@my_decorator
def hello():
  print("你好,装饰器")

@my_decorator
def hello2():
  print("你好,装饰器2")

hello2()

输出:

这是装饰后具有的新输出
你好,装饰器2
[Finished in 0.1s]

三、带参数的装饰器

1. 单个参数

上面的只是一个非常简单的装饰器,但是实际场景中,很多函数都是要带有参数的,比如hello(people_name)。

其实也很简单,要什么我们就给什么呗,直接在对应装饰器的wrapper()上,加上对应的参数:

def my_decorator(func):
  def wrapper(people_name):
    print("这是装饰后具有的新输出")
    func(people_name)
  return wrapper

@my_decorator
def hello(people_name):
  print("你好,{}".format(people_name))

hello("张三")

输出:

这是装饰后具有的新输出
你好,张三
[Finished in 0.1s]

2. 多个参数

但是还没完,这样虽然简单,但是随之而来另一个问题:因为并不是所有函数参数都是一样的,
当其他要使用装饰器的函数参数不止这个一个肿么办?比如:

@my_decorator
def hello3(speaker, listener):
  print("{}对{}说你好!".format(speaker, listener))

没关系,在python里,*args**kwargs表示接受任意数量和类型的参数,所以我们可以这样
写装饰器里的wrapper()函数:

def my_decorator(func):
  def wrapper(*args, **kwargs):
    print("这是装饰后具有的新输出")
    func(*args, **kwargs)
  return wrapper

@my_decorator
def hello(people_name):
  print("你好,{}".format(people_name))

@my_decorator
def hello3(speaker, listener):
  print("{}对{}说你好!".format(speaker, listener))

hello("老王")
print("------------------------")
hello3("张三", "李四")

同时运行下hello("老王"),和hello3("张三", "李四"),看结果:

这是装饰后具有的新输出
你好,老王
------------------------
这是装饰后具有的新输出
张三对李四说你好!
[Finished in 0.1s]

3. 自定义参数

上面2种,装饰器都是接收外来的参数,其实装饰器还可以接收自己的参数。
比如,我加个参数来控制下装饰器中打印信息的次数:

def count(num):
  def my_decorator(func):
    def wrapper(*args, **kwargs):
      for i in range(num):
        print("这是装饰后具有的新输出")
        func(*args, **kwargs)
    return wrapper
  return my_decorator

@count(3)
def hello(people_name):
  print("你好,{}".format(people_name))

hello("老王")

注意,这里count装饰函数中的2个return.
运行下,应该会出现3次:

这是装饰后具有的新输出
你好,老王
这是装饰后具有的新输出
你好,老王
这是装饰后具有的新输出
你好,老王
[Finished in 0.1s]

4. 内置装饰器@functools.wrap

现在多做一步探索,我们来打印下下面例子中的hello()函数的元信息:

def my_decorator(func):
  def wrapper(*args, **kwargs):
    print("这是装饰后具有的新输出")
    func(*args, **kwargs)
  return wrapper

@my_decorator
def hello(people_name):
  print("你好,{}".format(people_name))

print(hello.__name__) #看下hello函数的元信息

输出:

wrapper

这说明了,它不再是以前的那个 hello() 函数,而是被 wrapper() 函数取代了。

如果我们需要用到元函数信息,那怎么保留它呢?这时候可以用内置装饰器@functools.wrap

import functools

def my_decorator(func):
  @functools.wraps(func)
  def wrapper(*args, **kwargs):
    print("这是装饰后具有的新输出")
    func(*args, **kwargs)
  return wrapper

@my_decorator
def hello(people_name):
  print("你好,{}".format(people_name))

print(hello.__name__)

 运行下:

hello
[Finished in 0.1s]

四、类装饰器

装饰器除了是函数之外,也可以是类。

但是类作为装饰器的话,需要依赖一个函数__call__(),当调用这个类的实例时,函数__call__()就
会被执行。

来改造下之前的例子,把函数装饰器改成类装饰器:

class MyDecorator():
  def __init__(self, func):
    self.func = func

  def __call__(self, *args, **kwargs):
    print("这是装饰后具有的新输出")
    return self.func(*args, **kwargs)

# def my_decorator(func):
#   def wrapper():
#     print("这是装饰后具有的新输出")
#     func()
#   return wrapper

@MyDecorator
def hello():
  print("你好,装饰器")

hello()

运行:

这是装饰后具有的新输出
你好,装饰器
[Finished in 0.1s]

跟函数装饰器一样,实现一样的功能。

五、装饰器的嵌套

既然装饰器可以增强函数的功能,那如果有多个装饰器,我都想要怎么办?
其实,只要把需要用的装饰器都加上去就好了:

@decorator1
@decorator2
@decorator3
def hello():
  ...

但是要注意这里的执行顺序,会从上到下去执行,可以来看下:

def my_decorator(func):
  def wrapper():
    print("这是装饰后具有的新输出")
    func()
  return wrapper

def my_decorator2(func):
  def wrapper():
    print("这是装饰后具有的新输出2")
    func()
  return wrapper

def my_decorator3(func):
  def wrapper():
    print("这是装饰后具有的新输出3")
    func()
  return wrapper

@my_decorator
@my_decorator2
@my_decorator3
def hello():
  print("你好,装饰器")

hello()

运行

这是装饰后具有的新输出
这是装饰后具有的新输出2
这是装饰后具有的新输出3
你好,装饰器
[Finished in 0.1s]

好记性不如烂笔头,写一下理解一下会好很多。

以上就是通俗讲解python 装饰器的详细内容,更多关于python 装饰器的资料请关注三水点靠木其它相关文章!

Python 相关文章推荐
Python运行报错UnicodeDecodeError的解决方法
Jun 07 Python
python实现读取excel写入mysql的小工具详解
Nov 20 Python
flask中使用蓝图将路由分开写在不同文件实例解析
Jan 19 Python
pycharm: 恢复(reset) 误删文件的方法
Oct 22 Python
python之线程通过信号pyqtSignal刷新ui的方法
Jan 11 Python
Python面向对象程序设计之类的定义与继承简单示例
Mar 18 Python
python Tkinter的图片刷新实例
Jun 14 Python
python代码 FTP备份交换机配置脚本实例解析
Aug 01 Python
解决Keras中循环使用K.ctc_decode内存不释放的问题
Jun 29 Python
django模型类中,null=True,blank=True用法说明
Jul 09 Python
手残删除python之后的补救方法
Jun 26 Python
python实现简单聊天功能
Jul 07 Python
彻底搞懂python 迭代器和生成器
Sep 07 #Python
python如何设置静态变量
Sep 07 #Python
获取CSDN文章内容并转换为markdown文本的python
Sep 06 #Python
浅谈Python描述数据结构之KMP篇
Sep 06 #Python
详解Python3 定义一个跨越多行的字符串的多种方法
Sep 06 #Python
Python中实现一行拆多行和多行并一行的示例代码
Sep 06 #Python
Pytest单元测试框架如何实现参数化
Sep 05 #Python
You might like
Flash空降上海 化身大魔王接受挑战
2020/03/02 星际争霸
PHP获取文件的MD5值并判断是否被修改的例子
2014/06/19 PHP
Yii2使用小技巧之通过 Composer 添加 FontAwesome 字体资源
2014/06/22 PHP
php模拟登陆的实现方法分析
2015/01/09 PHP
PHP Ajax JavaScript Json获取天气信息实现代码
2016/08/17 PHP
PHP 接入支付宝即时到账功能
2016/09/18 PHP
关于PHP定时发送服务的解决办法
2017/04/23 PHP
javascript英文日期(有时间)选择器
2007/05/02 Javascript
js 获取和设置css3 属性值的实现方法
2013/05/06 Javascript
js 图片随机不定向浮动的实现代码
2013/07/02 Javascript
jQuery function的正确书写方法
2013/08/02 Javascript
js时间戳格式化成日期格式的多种方法
2013/11/11 Javascript
编写简单的jQuery提示插件
2014/12/21 Javascript
简单谈谈javascript中this的隐式绑定
2016/02/22 Javascript
关于Vue.js 2.0的Vuex 2.0 你需要更新的知识库
2016/11/30 Javascript
Angularjs中的ui-bootstrap的使用教程
2017/02/19 Javascript
nodejs个人博客开发第五步 分配数据
2017/04/12 NodeJs
详解利用jsx写vue组件的方法示例
2017/07/17 Javascript
IE11下使用canvas.toDataURL报SecurityError错误的解决方法
2017/11/19 Javascript
[03:02]生活中的Dendi之野外度假篇
2016/08/09 DOTA
Python中利用函数装饰器实现备忘功能
2015/03/30 Python
在CentOS上配置Nginx+Gunicorn+Python+Flask环境的教程
2016/06/07 Python
Python之re操作方法(详解)
2017/06/14 Python
Python管理Windows服务小脚本
2018/03/12 Python
Selenium自动化测试工具使用方法汇总
2020/06/12 Python
Python脚本破解压缩文件口令实例教程(zipfile)
2020/06/14 Python
python 基于opencv实现高斯平滑
2020/12/18 Python
表单button的outline在firefox浏览器下的问题
2012/12/24 HTML / CSS
Laura Geller官网:美国彩妆品牌
2018/12/29 全球购物
夜大毕业自我鉴定
2013/10/11 职场文书
初中三年学生的学习自我评价
2013/11/13 职场文书
大学生第一学年自我鉴定
2014/09/12 职场文书
社区党支部承诺书
2015/04/29 职场文书
工程进度款催款函
2015/06/24 职场文书
解决numpy和torch数据类型转化的问题
2021/05/23 Python
详解JAVA的控制语句
2021/11/11 Java/Android