大家好,我是菜鸟哥!

之前的文章中提到,很多人认为理解了装饰器 的概念和用法后,会觉得自己的 Python 水平有一个明显的提高。

但很多教程在一上来就会给出装饰器的定义以及基本用法,例如你一定会在很多文章中看到例如代码运行时间计时器等相关常用装饰器。

直接从应用入手这样学习当然十分有效,但不是看过就忘就是似懂非懂的状态,因为装饰器从来就不是一个单独的概念,就像数学分析中求积分一样,你可以通过公式快速算出需要求的积分,但是若明白积分是由极限定义的,之后再看积分将会是不一样的视角。

本文我将尝试说清楚为什么需要现装饰器、什么是装饰器、以及如何写一个简单的装饰器,但要彻底理解装饰器还要从函数开始说起,下面是有关函数的四个重要的概念,希望大家可以明白。

01

有关函数的四个重要概念

相信你在大多数文章中,至少也能知道例如「装饰器是装饰函数」,「在不修改函数代码的情况下增加额外功能」等核心概念,但首先要知道为什么函数能够被装饰。

例如在《流畅的Python》一书中,讲到函数的一开始就提出了一个概念,函数是一等对象

正如书中所说,在Python中一个函数既可以作为参数被传递,也能作为另一个函数的返回值,这也是函数可以被装饰的关键,在介绍装饰器之前,下面有必要通过简单的代码对这段话做一个更直观的理解。

1.1 函数中传递函数

函数中传递函数意思就是可以将函数当作变量来使用,我们来看一个简单的示例。

在下面的代码中,func1是一个普通的函数,接受两个参数a,b并返回他们的和。func2不一样的地方在于多接收了一个func参数,这个func变量需要是一个函数

def func1(a,b):print(f"函数 {func1.__name__} 正在执行")return a + bdef func2(func,c,d):print(f"函数 {func2.__name__} 正在执行")return func(c,d)

现在让我们来执行func1

>>> func1(1,2)
函数 func1 正在执行
3

下面func1作为参数执行func2

>>> func2(func1,3,4)
函数 func2 正在执行
函数 func1 正在执行
7

可以看到,先执行func2,在func2接收到fun1后,再次执行func1并返回。注意这里的func1没有括号,它只不过是和a,b一样的参数被使用,理解这点后我们继续看下一个知识点。

1.2 函数中定义函数

在定义一个函数后,可以继续在函数内部定义新的函数。为了理解这点,我们来看下面简单的示例。

我们先定义了一个函数func1,并在func1中定义了func2,并在func1的内部调用了func2

def func1():print(f"函数 {func1.__name__} 正在执行")def func2():print(f"内部函数 {func2.__name__} 正在执行")func2()

现在执行func1func2看看会发生什么

>>> func1()
函数 func1 正在执行
内部函数 func2 正在执行>>> func2()
------------------------------------------------
Traceback (most recent call last)----> 1 func2()NameError: name 'func2' is not defined

可以看到,当执行func1时,会自动执行func2,但是如果单独执行func2,则提示未定义,说明func2只能在func1中被调用!

1.3 函数返回函数

最后是一个函数可以将另一个函数作为返回值返回的简单示例,在下面的代码中,我们先定义了一个外部函数func1(接受一个参数a),之后定义了一个内部函数func2(接受一个参数b)并返回a + b,最后将func2作为func1的返回值返回

def func1(a):print(f"函数 {func1.__name__} 正在执行")def func2(b):print(f"函数 {func2.__name__} 正在执行")return a + breturn func2

需要注意的是,这里返回的func2没有括号,代表返回的是func2的地址!

>>> func3 = func1(1)
>>> func3
函数 func1 正在执行
<function __main__.func1.<locals>.func2(b)>>>> func3(2)
函数 func2 正在执行
3

从上面的运行结果可以看到,当执行func1(1)后,返回的是func2的地址,并赋给func3,之后执行func3(2)才真正执行了内部函数func2

现在我们就解决了上一小节的问题「将内部函数func2单独拿出来用」!

1.4 函数内省

函数内省是相对来说比较好理解的一个概念,在Python中的意思就是我们可以访问函数的部分属性,例如print函数,可以使用dir函数来查看其全部属性

>>> dir(print)
['__call__','__class__','__delattr__',··· ···'__subclasshook__','__text_signature__']

现在可以查看其对应的属性

>>> print.__name__
'print'
>>> print.__call__
<method-wrapper '__call__' of builtin_function_or_method object at 0x7fddb8056b80>
>>> print.__doc__
"print(value, ..., sep=' ', end='\\n', file=sys.stdout, flush=False)\n\nPrints the values to a stream, or to sys.stdout by default.\nOptional keyword arguments:\nfile:  a file-like object (stream); defaults to the current sys.stdout.\nsep:   string inserted between values, default a space.\nend:   string appended after the last value, default a newline.\nflush: whether to forcibly flush the stream."

函数内省了解到这个程度即可,我们会在2.3节再次提到这里的知识!

至此,我已经将接触装饰器之前必须要吃透的知识点介绍完毕,如果你觉得我讲解的不够清晰,可以查看任何其他教程或者书籍弄懂后再继续阅读。

02

初识装饰器

现在终于可以来说说装饰器了,当然绝对不是直接告诉你一个写好的装饰器,而是我们一点一点去写一个简单的装饰器。

2.1 第一个装饰器

在下面的代码中,我们先定义了一个函数first_decorator,该函数接受函数为参数(如果不理解请查看本文 1.1 节),之后在内部定义了一个名为name_wrapper的内部函数(如果不理解请查看本文 1.2 节),最后返回以name_wrapper作为返回值(如果不理解请查看本文 1.3 节)

def first_decorator(func):def name_wrapper():print(f"被装饰的函数 {func.__name__} 即将执行")func()print(f"被装饰的函数 {func.__name__} 执行完毕")return name_wrapper

这个函数的功能是,在执行被接收函数前后分别打印一段话,所以我们要再定义一个函数来测试效果

def add():print("函数 add 正在执行 ")

这个fun1没有什么好说的,打印一段话。下面需要仔细看了,我们来执行这两段代码

>>> add = first_decorator(add)
>>> add()
被装饰的函数 add 即将执行
函数 add 正在执行
被装饰的函数 add 执行完毕

正如我们预料的一样,在执行add前后都有一段提示,但是如果每次使用first_decorator功能都需要先将add传递,之后再调用,来回写好几遍,实在太麻烦了!

因此这完全有更Pythonic的写法,也就是我们常见的装饰器形式,使用语法糖@,例如上面的例子和下面的写法等价

@first_decorator
def add():print("函数 add 正在执行 ")

@+装饰器函数名字放在需要被装饰函数的上方即可,现在直接调用add即可实现装饰器的功能!

>>> add()
被装饰的函数 add 即将执行
函数 add 正在执行
被装饰的函数 add 执行完毕

相信看到这里,你应该明白装饰器@如何工作的,至少你在未来看到@时需要想到类似等价于add = first_decorator(add)一样的作用!

2.2 装饰器传参

上面仅是最简单的装饰器示例,在实际使用时

很自然的想法那就是加上参数呗,改起来也不难

@first_decorator
def add(x,y):print("函数 add 正在执行 ")print(f"{x} + {y} 的结果为{x+y}")

让我们来测试一下

>>> add(1,2)
-----------------------------------------------
Traceback (most recent call last)
<ipython-input-144-944f4051a32c> in <module>
----> 1 add(1,2)TypeError: name_wrapper() takes 0 positional arguments but 2 were given

不出意外的报错了,虽然我们给被装饰的函数加上了参数,但是在装饰器的内部函数name_wrapper()执行时并没有参数!

因此我们之前的代码可以这么改,使用*args, **kwargs也是非常常见的用法

def first_decorator(func):def name_wrapper(*args, **kwargs):print(f"被装饰的函数 {func.__name__} 即将执行")func(*args, **kwargs)print(f"被装饰的函数 {func.__name__} 执行完毕")return name_wrapper

现在我们再次使用这个装饰器即可返回我们预测的结果!

@first_decorator
def add(x,y):print("函数 add 正在执行 ")print(f"{x} + {y} 的结果为 {x+y}")>>> add(1,2)
被装饰的函数 add 即将执行
函数 add 正在执行
1 + 2 的结果为 3
被装饰的函数 add 执行完毕

03

functools

本文的最后,还需要简单介绍一下在写装饰器时常用的functools模块。

还记得1.4节的函数内省相关知识吗?我们可以打印一个函数指向的内存地址或者名字等其他属性。

还是上面用到的add函数,我们都知道虽然被装饰了,但是功能上没有任何变化,依旧是计算两个数字的和,但是真的没有任何变化吗?下面让我们来观察一下

def add(x,y):print("函数 add 正在执行 ")print(f"{x} + {y} 的结果为{x+y}")>>> print(add)
<function add at 0x7fddb9dd41f0>
>>>print(add.__name__)
add@first_decorator
def add(x,y):print("函数 add 正在执行 ")print(f"{x} + {y} 的结果为{x+y}")>>> print(add)
<function first_decorator.<locals>.name_wrapper at 0x7fddb9dd4e50>
>>> print(add.__name__)
name_wrapper

可以看到,被装饰后,虽然功能上没有变化,但是它指向的是装饰器所定义的内部函数!这并不是我们希望看到的,比如若不同函数被两个装饰器装饰时则会出现一样的函数名!

幸运的是Python中的functools库可以轻松解决这个问题,只需要加上一行简单的代码就可以搞定!

import functoolsdef first_decorator(func):@functools.wraps(func)def name_wrapper(*args, **kwargs):print(f"被装饰的函数 {func.__name__} 即将执行")func(*args, **kwargs)print(f"被装饰的函数 {func.__name__} 执行完毕")return name_wrapper@first_decorator
def add(x,y):print("函数 add 正在执行 ")print(f"{x} + {y} 的结果为{x+y}")>>> print(add)
<function add at 0x7fddb9dd4e50>
>>> print(add.__name__)
add

可以看到此时函数名等函数属性均保留下来了,事实上@functools.wraps(func)通过functools.update_wrapper()将原函数中的部分内省属性固定,只传递部分关键参数来实现这个功能,感兴趣的读者可以自行进一步研究。

至此,我想你应该明白为什么需要现装饰器、什么是装饰器、以及如何写一个简单的装饰器,当你再次看到装饰器时,脑海中浮现的概念应该不仅仅是@。有关装饰器更高级的用法,以及一些常见、好用的装饰器,我将在装饰器的第二篇文章中进行介绍!

左手Python,右手Java,升职就业不愁啦!

推荐阅读:入门: 最全的零基础学Python的问题  | 零基础学了8个月的Python  | 实战项目 |学Python就是这条捷径干货:爬取豆瓣短评,电影《后来的我们》 | 38年NBA最佳球员分析 |   从万众期待到口碑扑街!唐探3令人失望  | 笑看新倚天屠龙记 | 灯谜答题王 |用Python做个海量小姐姐素描图 |碟中谍这么火,我用机器学习做个迷你推荐系统电影趣味:弹球游戏  | 九宫格  | 漂亮的花 | 两百行Python《天天酷跑》游戏!AI: 会做诗的机器人 | 给图片上色 | 预测收入 | 碟中谍这么火,我用机器学习做个迷你推荐系统电影小工具: Pdf转Word,轻松搞定表格和水印! | 一键把html网页保存为pdf!|  再见PDF提取收费! | 用90行代码打造最强PDF转换器,word、PPT、excel、markdown、html一键转换 | 制作一款钉钉低价机票提示器! |60行代码做了一个语音壁纸切换器天天看小姐姐!|年度爆款文案1).卧槽!Pdf转Word用Python轻松搞定!2).学Python真香!我用100行代码做了个网站,帮人PS旅行图片,赚个鸡腿吃3).首播过亿,火爆全网,我分析了《乘风破浪的姐姐》,发现了这些秘密 4).80行代码!用Python做一个哆来A梦分身 5).你必须掌握的20个python代码,短小精悍,用处无穷 6).30个Python奇淫技巧集 7).我总结的80页《菜鸟学Python精选干货.pdf》,都是干货 8).再见Python!我要学Go了!2500字深度分析!9).发现一个舔狗福利!这个Python爬虫神器太爽了,自动下载妹子图片点阅读原文,领AI全套资料

人人都能看懂的 Python 装饰器入门教程!相关推荐

  1. 人人都能看懂的 Python 装饰器入门教程

    大家好,我是萱萱! 之前的文章中提到,很多人认为理解了装饰器的概念和用法后,会觉得自己的 Python 水平有一个明显的提高. 但很多教程在一上来就会给出装饰器的定义以及基本用法,例如你一定会在很多文 ...

  2. 人人都能看懂LSTM

    这是在看了台大李宏毅教授的深度学习视频之后的一点总结和感想.看完介绍的第一部分RNN尤其LSTM的介绍之后,整个人醍醐灌顶.本篇博客就是对视频的一些记录加上了一些个人的思考. 0. 从RNN说起 循环 ...

  3. 人人都能看懂的LSTMGRU

    看过的讲的最简单明了的: LSTM:人人都能看懂的LSTM GRU:人人都能看懂的GRU 自己对LSTM的理解与代码解释:https://blog.csdn.net/Strive_For_Future ...

  4. 【图解】连狗子都能看懂的Python基础总结(二)什么是库、包、模块?

    [图解]连狗子都能看懂的Python基础总结!(二)什么是库.包.模块? 本章内容 什么是模块? 什么是包? 什么是库? 什么是标准库和第三方库? 上次,我们解释了"变量".&qu ...

  5. 人人都能看懂的Spring源码解析,Spring如何解决循环依赖

    人人都能看懂的Spring源码解析,Spring如何解决循环依赖 原理解析 什么是循环依赖 循环依赖会有什么问题? 如何解决循环依赖 问题的根本原因 如何解决 为什么需要三级缓存? Spring的三级 ...

  6. 人人都能看懂的Spring底层原理,看完绝对不会懵逼

    人人都能看懂的Spring原理,绝对不会懵逼 为什么要使用Spring? Spring的核心组件 Spring是如何实现IOC和DI的? 定义了BeanDefinition 扫描加载BeanDefin ...

  7. 人人都能看懂的EM算法推导

    作者丨August@知乎(已授权) 来源丨https://zhuanlan.zhihu.com/p/36331115 编辑丨极市平台 估计有很多入门机器学习的同学在看到EM算法的时候会有种种疑惑:EM ...

  8. em算法 实例 正态分布_人人都能看懂的EM算法推导

    ↑ 点击蓝字 关注极市平台作者丨August@知乎(已授权)来源丨https://zhuanlan.zhihu.com/p/36331115编辑丨极市平台 极市导读 EM算法到底是什么,公式推导怎么去 ...

  9. 《小学生都能看懂的快速沃尔什变换从入门到升天教程》(FWT / FMT / FMI)(最最严谨清晰的证明!零基础也能得学会!)

    整理的算法模板合集: ACM模板 点我看算法全家桶系列!!! 实际上是一个全新的精炼模板整合计划 目录 0x00 卷积 0x01 多项式 0x02 卷积的定义 0x03 卷积的基本性质 0x04 位运 ...

最新文章

  1. 复数乘法_leetcode No.537 复数乘法
  2. 快速创建 shell脚本
  3. 用 Flask 来写个轻博客 (37) — 在 Github 上为第一阶段的版本打 Tag
  4. 接口(Interface)的深入理解
  5. codewars048: Triple Double
  6. Community Server系列之二:页面之间的关系1[介绍]
  7. 你见过动物是怎么笑的吗?赶紧来看看【组图】
  8. php7.1 split,PHP 函数 split()
  9. Delphi工具之Image Editor
  10. View Flash AS3 and AVM2
  11. 连接服务器框架协议,想要实现:设备通过tcp连接服务器,框架用的是worker或者gateway...
  12. golang slice分割和append copy还是引用
  13. ecshop添加商品选择品牌时如何按拼音排序
  14. 知乎面试官:为什么不建议在 MySQL 中使用 UTF-8?
  15. 基于Java swing实现的学生选课管理系统
  16. 初探TVM--TVM优化resnet50
  17. Unity 粒子特效看不见
  18. ht for web(图扑)加载模型
  19. 案例实战|泰坦尼克号船员获救预测(数据预处理部分)
  20. 怎样制作Lrc歌词文件

热门文章

  1. aws mysql 迁移_Amazon DMS 数据库迁移_数据迁移_云迁移-AWS云服务
  2. mingw跟MSVC库的相互转换
  3. STM32F4移植MLX90614(I2C)
  4. 一个中年大叔一次性通过系统集成项目管理工程师的心路历程
  5. [RK3568 Android11] 开发之user版本关闭selinux
  6. 详述 MIMIC护理人员信息表(十五)
  7. opencv 读取 16bit tif文件
  8. Android service学习笔记
  9. python 根据树型结构生成指定格式的excel数据
  10. 第25节 IPsec虚拟专网工作原理与配置详解