通俗讲解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解析xml模块封装代码
Feb 07 Python
Django的HttpRequest和HttpResponse对象详解
Jan 26 Python
Python Xml文件添加字节属性的方法
Mar 31 Python
django 使用 request 获取浏览器发送的参数示例代码
Jun 11 Python
python中的二维列表实例详解
Jun 19 Python
在PyCharm下打包*.py程序成.exe的方法
Nov 29 Python
Python基于plotly模块实现的画图操作示例
Jan 23 Python
python面向对象法实现图书管理系统
Apr 19 Python
妙用itchat! python实现久坐提醒功能
Nov 25 Python
python opencv进行图像拼接
Mar 27 Python
python操作微信自动发消息的实现(微信聊天机器人)
Jul 14 Python
python3读取文件指定行的三种方法
May 24 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
一首老MP3,致敬WAR3经典
2021/03/08 魔兽争霸
PHP.MVC的模板标签系统(一)
2006/09/05 PHP
如何用php生成扭曲及旋转的验证码图片
2013/06/07 PHP
PHP自动生成后台导航网址的最佳方法
2013/08/27 PHP
php+ajax实现无刷新的新闻留言系统
2020/12/21 PHP
thinkPHP基于ajax实现的菜单与分页示例
2016/07/12 PHP
PHP超全局变量实现原理及代码解析
2020/09/01 PHP
初学prototype,发个JS接受URL参数的代码
2006/09/25 Javascript
基于jQuery实现的QQ表情插件
2015/08/25 Javascript
javascript伸缩菜单栏实现代码分享
2015/11/12 Javascript
jQuery插件Validation快速完成表单验证的方式
2016/07/28 Javascript
javascript 注释代码的几种方法总结
2017/01/04 Javascript
PHP实现记录代码运行时间封装类实例教程
2017/05/08 Javascript
Vue.js中的图片引用路径的方式
2017/07/28 Javascript
为输入框加入数字js校验代码分享
2017/11/02 Javascript
Vue三种常用传值示例(父传子、子传父、非父子)
2018/07/24 Javascript
bootstrap 日期控件 datepicker被弹出框dialog覆盖的解决办法
2019/07/09 Javascript
关于JS解构的5种有趣用法
2019/09/05 Javascript
原生JavaScript写出Tabs标签页的实例代码
2020/07/20 Javascript
jquery自定义组件实例详解
2020/12/31 jQuery
Python中的异常处理简明介绍
2015/04/13 Python
Python网络编程之TCP与UDP协议套接字用法示例
2018/02/02 Python
Python实现求一个集合所有子集的示例
2018/05/04 Python
对python 生成拼接xml报文的示例详解
2018/12/28 Python
利用ImageAI库只需几行python代码实现目标检测
2019/08/09 Python
Python类中的魔法方法之 __slots__原理解析
2019/08/26 Python
python中有关时间日期格式转换问题
2019/12/25 Python
python使用布隆过滤器的实现示例
2020/08/20 Python
事业单位分类改革实施方案
2014/03/21 职场文书
2014年国庆标语
2014/06/30 职场文书
2014教师教育实践活动对照检查材料思想汇报
2014/09/21 职场文书
男方婚礼答谢词
2015/01/20 职场文书
幼儿园父亲节活动总结
2015/02/12 职场文书
学困生转化工作总结
2015/08/13 职场文书
python 开心网和豆瓣日记爬取的小爬虫
2021/05/29 Python
Python Pygame实战在打砖块游戏的实现
2022/03/17 Python