通俗讲解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 相关文章推荐
使用wxpython实现的一个简单图片浏览器实例
Jul 10 Python
python输入错误密码用户锁定实现方法
Nov 27 Python
Python实现在某个数组中查找一个值的算法示例
Jun 27 Python
python format 格式化输出方法
Jul 16 Python
使用Python实现将list中的每一项的首字母大写
Jun 11 Python
django实现用户注册实例讲解
Oct 30 Python
Django 实现Admin自动填充当前用户的示例代码
Nov 18 Python
Python操作redis和mongoDB的方法
Dec 19 Python
使用python turtle画高达
Jan 19 Python
python 已知平行四边形三个点,求第四个点的案例
Apr 12 Python
浅谈tensorflow 中的图片读取和裁剪方式
Jun 30 Python
python 如何用urllib与服务端交互(发送和接收数据)
Mar 04 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字符串 ==比较运算符的副作用
2009/10/21 PHP
php数据库抽象层 PDO
2011/05/07 PHP
PHP生成可点击刷新的验证码简单示例
2016/05/13 PHP
JavaScript 无符号右移运算符
2009/04/17 Javascript
jQuery学习4 浏览器的事件模型
2010/02/07 Javascript
js使用DOM操作实现简单留言板的方法
2015/04/10 Javascript
JS组件系列之Bootstrap Icon图标选择组件
2016/01/28 Javascript
JavaScript 浏览器兼容性总结及常用浏览器兼容性分析
2016/03/30 Javascript
js控制li的隐藏和显示实例代码
2016/10/15 Javascript
微信小程序 flex实现导航实例详解
2017/04/26 Javascript
如何理解Vue的作用域插槽的实现原理
2017/08/19 Javascript
js获取html页面代码中图片地址的实现代码
2018/03/05 Javascript
vue路由--网站导航功能详解
2019/03/29 Javascript
[01:56]林书豪DOTA2上海特级锦标赛励志短片
2016/03/05 DOTA
python单链表实现代码实例
2013/11/21 Python
python下载文件时显示下载进度的方法
2015/04/02 Python
Python中List.count()方法的使用教程
2015/05/20 Python
python使用tensorflow深度学习识别验证码
2018/04/03 Python
Django渲染Markdown文章目录的方法示例
2019/01/02 Python
Python实现图片转字符画的代码实例
2019/02/22 Python
python模块常用用法实例详解
2019/10/17 Python
python使用HTMLTestRunner导出饼图分析报告的方法
2019/12/30 Python
python连接mongodb集群方法详解
2020/02/13 Python
django在开发中取消外键约束的实现
2020/05/20 Python
如何利用python之wxpy模块玩转微信
2020/08/17 Python
CSS3 media queries + jQuery实现响应式导航
2016/09/30 HTML / CSS
使用HTML5拍照示例代码
2013/08/06 HTML / CSS
详解如何解决canvas图片getImageData,toDataURL跨域问题
2018/09/17 HTML / CSS
西班牙著名的珠宝首饰品牌:P D PAOLA
2018/09/15 全球购物
货代行业个人求职简历的自我评价
2013/10/22 职场文书
红领巾广播站广播稿
2014/02/01 职场文书
个人承诺书格式范文
2015/04/29 职场文书
晚会主持人开场白台词
2015/05/28 职场文书
小学语文教学随笔
2015/08/14 职场文书
创业计划书之校园跑腿公司
2019/09/24 职场文书
日本官方排名前10的动漫,名侦探柯南上榜,第一是一部创造历史的动漫
2022/03/18 日漫