通俗讲解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批量导出导入MySQL用户的方法
Nov 15 Python
用Python代码来绘制彭罗斯点阵的教程
Apr 03 Python
浅谈python和C语言混编的几种方式(推荐)
Sep 27 Python
python中matplotlib的颜色及线条控制的示例
Mar 16 Python
Python中一些不为人知的基础技巧总结
May 19 Python
Python 查看list中是否含有某元素的方法
Jun 27 Python
解决Python3用PIL的ImageFont输出中文乱码的问题
Aug 22 Python
python3 实现爬取TOP500的音乐信息并存储到mongoDB数据库中
Aug 24 Python
PyTorch使用cpu加载模型运算方式
Jan 13 Python
xadmin使用formfield_for_dbfield函数过滤下拉表单实例
Apr 07 Python
python对一个数向上取整的实例方法
Jun 18 Python
解决python 在for循环并且pop数组的时候会跳过某些元素的问题
Dec 11 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
php通过array_unshift函数添加多个变量到数组前端的方法
2015/03/18 PHP
PHP版本升级到7.x后wordpress的一些修改及wordpress技巧
2015/12/25 PHP
PHP简单检测网址是否能够正常打开的方法
2016/09/04 PHP
php魔法函数与魔法常量使用介绍
2017/07/23 PHP
php 字符串中是否包含指定字符串的多种方法
2018/04/12 PHP
关于B/S判断浏览器断开的问题讨论
2008/10/29 Javascript
jQuery EasyUI API 中文文档 DateTimeBox日期时间框
2011/10/16 Javascript
js change,propertychange,input事件小议
2011/12/20 Javascript
JS获取鼠标坐标的实例方法
2013/07/18 Javascript
escape函数解决js中ajax传递中文出现乱码问题
2014/10/30 Javascript
bootstrap实现图片自动轮播
2016/12/21 Javascript
多个上传文件用js验证文件的格式和大小的方法(推荐)
2017/03/09 Javascript
JS实现在文本指定位置插入内容的简单示例
2017/12/22 Javascript
Vue 中mixin 的用法详解
2018/04/23 Javascript
解决element UI 自定义传参的问题
2018/08/22 Javascript
JavaScript实现省市联动效果
2019/11/22 Javascript
JavaScript中常用的3种弹出提示框(alert、confirm、prompt)
2020/11/10 Javascript
使用JS实现鼠标放上图片进行放大离开实现缩小功能
2021/01/27 Javascript
python 多线程应用介绍
2012/12/19 Python
利用python爬取斗鱼app中照片方法实例
2017/12/03 Python
python+selenium实现163邮箱自动登陆的方法
2017/12/31 Python
pyqt5 实现多窗口跳转的方法
2019/06/19 Python
详解Python中如何将数据存储为json格式的文件
2020/11/18 Python
澳大利亚新奇小玩意网站:Yellow Octopus
2017/12/28 全球购物
如何防止同一个帐户被多人同时登录
2013/08/01 面试题
大学应届毕业生个人求职信
2013/09/23 职场文书
国际贸易专业推荐信
2013/11/15 职场文书
结婚典礼证婚词
2014/01/11 职场文书
就业推荐表自我鉴定
2014/03/21 职场文书
数学考试作弊检讨书300字
2015/02/16 职场文书
2016大学生毕业实习心得体会
2016/01/23 职场文书
慰问信(范文3篇)
2019/10/23 职场文书
导游词之平津战役纪念馆
2019/11/04 职场文书
【HBU】数据库第四周 单表查询
2021/04/05 SQL Server
Nginx性能优化之Gzip压缩设置详解(最大程度提高页面打开速度)
2022/02/12 Servers
Win11如何设置右键单击显示所有选项?Win11右键单击显示所有选项设置教程
2022/04/08 数码科技