1 一切都要从函数说起

我们都知道一个函数可以返回一些数据,然后这些数据可以被其他函数调用。函数里还可以有若干个参数,可以让函数根据不同的输入值进行不同的计算,然后得到新的结果。

于是,我们的故事就可以开始了。

现有第一个函数,tony,很简单,直接返回tony,然后你叫他一声。他就回应你:tony。

def tony():return 'tony'
tony()
'tony'

紧接着,我们来到第二个函数,他可以根据传入的一个男人man,然后说出谁是钢铁侠。

def iron_man(man):print(f'{man} is iron man!')

由于在Python里面任何东西都是个对象,函数也不例外,我们就直接把上面的tony()(注意括号啊!)直接传给iron_man,会得到什么呢?
如你所见,结果很简单,当做参数的函数,返回值传入了新的函数,然后组合出现了我们看到的结果。

iron_man(tony())
tony is iron man!

刚才只是简单的说一下谁是钢铁侠,下面他就要变身了。

既然函数可以当做参数,那么我们在定义复杂一点的函数。如下所示,把函数名func当做参数,然后嵌套写一个包装函数 wrapper,这个函数里面调用func,这时候是有括号的,当做他的变身。
同时这行代码的上下,分别添加语句,分别显示变身前和变身后。

这个函数的最后,返回的是内部包裹的函数名,其实就是个函数对象,但是,没有调用呢。

# tony要变身def deco_iron(func):def wrapper():print('开始准备变身')man = func()print(f'{man} 正在变身!')print('变身结束!')return wrapper

接下来,我们调用这个复杂函数,就可以完成钢铁侠的变身了。和刚才不同的是,注意我们只是传入函数名。因为这个函数要在刚才包裹他的函数内部运行。
我们可以给个新的变量new_tony,注意咯,这时候得到的是刚才复杂函数的返回值,也就是内部的函数对象。如果要调用,需要加括号的。

new_tony = deco_iron(tony)  # 注意!!没有括号,去变身里面执行

试一下就知道了。

new_tony()
开始准备变身tony 正在变身!变身结束!

如你所见,变身成功。下面问题来了:有没有可能直接运行tony就达到效果?

当然有,我们把刚才的返回值变量直接写作tony,运行看看:

tony = deco_iron(tony)tony()
开始准备变身tony 正在变身!变身结束!

回顾一下,我们现在相当于还是调用名为tony的函数,但是,在没有改变原来函数的情况下,我们给他添加了整套的变身语句。如同钢铁盔甲增强了tony的身体素质,我们用了一个嵌套函数增强了刚才简简单单的tony函数,但其实从头到尾没有修改原来的tony函数。这就给代码增添了无限的可能。

但,这样调用来调用去的有点啰嗦,需要简化。

2 装饰器@魔法出现了

Python是一个以简洁著称的语言,刚才那种写完嵌套函数,调用一下,又返回同样的函数名,然后等着再调用的做法,直接可以用一个@符号搞定。
这时候的@相当于tony = deco_iron(tony),然后直接加到原来的函数上头就ok了。如下所示:

@deco_irondef tony():return 'tony'

调用一下这个函数,你会发现已经被装饰加强了。这就是装饰器了。

tony()
开始准备变身tony 正在变身!变身结束!

3 装饰器的变身

传递参数

函数是可以传递参数的,如果我们想让被装饰的函数传递参数可以这样做:

  • 正常添加任何参数,比如我们这里加个msg

  • 在装饰器函数的内部函数中,使用*args, **kw接收任何参数就ok了

为了刚方便查看测试效果,我们这里再用func.__name__格式化输出一下函数名。

# 传递参数def deco_iron(func):print('deco_iron开始运行....')def wrapper(*args, **kw):print('开始变身,在执行{}函数前'.format(func.__name__))print(args, kw)func(*args, **kw)print('钢铁侠变身完成,结束{}函数'.format(func.__name__))return wrapper

下面,从新装饰一下tony函数,你会发现,定义之后就会自动运行上面装饰器函数的第一句话。其实这就是在Python内部,自动完成了tony = deco_iron(tony),悄悄第一次运行了整个装饰器函数。接下来我们要调用一下下面的函数,才会运行外部函数的返回结果,从而进入的最内部的函数。

@deco_irondef tony(msg):print(f'tony说:{msg}')
deco_iron开始运行....
tony('我来啦!')
开始变身,在执行tony函数前('我来啦!',) {}tony说:我来啦!钢铁侠变身完成,结束tony函数

多重装饰器

单层的装饰器说完了。下面说一下多重装饰器。比如钢铁侠,有时候要去和更强大的敌人战斗,需要多穿一层盔甲。

于是,DE8UG新写个带new后缀的函数,同时在里面适当修改提示语句。

# 多重装饰器def deco_iron_new(func):print('deco_iron_new开始运行....')def wrapper(*args, **kw):print('deco_iron_new 开始变身,在执行{}函数前'.format(func.__name__))print(args, kw)func(*args, **kw)print('deco_iron_new 钢铁侠变身完成,结束{}函数'.format(func.__name__))return wrapper

接下来就是使用了。只需要在要装饰的函数上,依次往上叠加@装饰函数名,就ok。这时候,定义语句被执行时候,你会发现同样执行了装饰器里面的第一句说明。

看一下执行顺序,是按照装饰顺序,从内到外执行的。

@deco_iron_new@deco_irondef tony(msg):print(f'tony说:{msg}')
deco_iron开始运行....deco_iron_new开始运行....

但,我们需要注意的是,当实际执行tony函数时,会发生什么,先看结果再解释。

tony('我要超级变身')
deco_iron_new 开始变身,在执行wrapper函数前('我要超级变身',) {}开始变身,在执行tony函数前('我要超级变身',) {}tony说:我要超级变身钢铁侠变身完成,结束tony函数deco_iron_new 钢铁侠变身完成,结束wrapper函数

你会发现一个有意思的现象,实际执行的函数,是按照装饰的层次,从外到内执行了。

为什么呢?
仔细思考上文的装饰器语法含义,我们会发现刚才定义时,从内到外执行的多个装饰器函数,每个装饰器函数都返回一个函数对象。这时候最外层的返回函数对象当然就在最外面了。
当被装饰后的函数具体执行时,就像剥洋葱一样,从外到内,一层层的执行具体的语句。

发现函数名问题

相信眼尖的同学看出来了,刚才多层装饰器一次出现的说明语句,对于函数名的解析,似乎有点问题,最外层的没有正确说明要执行的tony函数。

这是为什么?

在函数执行的时候,会有自己的变量作用域,当多层嵌套的装饰器执行时,虽然运行不出错,但其实内部的作用域出现的混乱。比如我们看见的错误的显示了内嵌函数的名称。

这个怎么解决呢,Python自己带来的问题,当然可以自己解决。直接使用functools里的wraps装饰器,来修复这个作用域问题。用法就是在内嵌函数上装饰一下,记住原函数信息。

再次运行一下,你会发现函数名已经正常显示了。

from functools import wraps# 多重装饰器def deco_iron_new(func):print('deco_iron_new 开始运行....')@wraps(func)def wrapper(*args, **kw):print('deco_iron_new 开始变身,在执行{}函数前'.format(func.__name__))print(args, kw)func(*args, **kw)print('deco_iron_new 钢铁侠变身完成,结束{}函数'.format(func.__name__))return wrapper# 传递参数def deco_iron(func):print('deco_iron开始运行....')@wraps(func)def wrapper(*args, **kw):print('开始变身,在执行{}函数前'.format(func.__name__))print(args, kw)func(*args, **kw)print('钢铁侠变身完成,结束{}函数'.format(func.__name__))return wrapper
@deco_iron_new@deco_irondef tony(msg):print(f'tony说:{msg}')
deco_iron开始运行....deco_iron_new 开始运行....
tony('再次变身')
deco_iron_new 开始变身,在执行tony函数前('再次变身',) {}开始变身,在执行tony函数前('再次变身',) {}tony说:再次变身钢铁侠变身完成,结束tony函数deco_iron_new 钢铁侠变身完成,结束tony函数

如果装饰器加参数呢

DE8UG小提示:以后如何查询此文档以及获取其他帮助?
在手机上搜索「Python随身听」官方号,点击右上角「...」,进入主题页面,找到页面上方的放大镜?,点击放大镜?搜索关键词即可。

下面再来个复杂点的。既然装饰器本身还是函数,那么当然可以继续添加参数。

怎么加呢?
再嵌套一层函数,把参数传给它!

比如,钢铁侠要带着小辣椒一起飞,可以这么写:

# 如果装饰器加参数呢?def deco_params(name):def deco_iron(func):print('deco_iron开始运行....')@wraps(func)def wrapper(*args, **kw):print('新参数需要处理:', name)print('开始变身,在执行{}函数前'.format(func.__name__))func(*args, **kw)print('钢铁侠变身完成,结束{}函数'.format(func.__name__))return wrapperreturn deco_iron@deco_params('小辣椒')def tony(msg):print(f'tony说:{msg}')
deco_iron开始运行....
tony('hi, 小辣椒')
新参数需要处理:小辣椒开始变身,在执行tony函数前tony说:hi, 小辣椒钢铁侠变身完成,结束tony函数

4 装饰器的用途

Python中,装饰器的用途很多。我们举一个web应用的例子。

我们都知道一个网站往往会有多个连接,根据不同的连接地址,进行路由转发,可以显示不同的业务内容。于是,我们可以这样定义一个类MyApp,表示网络应用。

里面放置三个函数,完成路由转发的功能:

  • 初始化函数,放置一个字典

  • 注册函数,用来当其他业务函数的装饰器。这是个嵌套函数,里面可以根据不同的路由页面名称,把函数名保存到字典

  • 实际页面调用函数。

接下来使用时,可以很方便把不同页面的响应函数,注册到不同的路由下面。当访问不同页面时,根据响应的参数直接调用同一个方法,就能自动去转换到对应的页面内容了。

# 装饰器,各种用途# author: De8uGclass MyApp():def __init__(self):self.func_map = {}def register(self, name):def func_wrapper(func):print('func_wrapper: ', name, func)self.func_map[name] = funcreturn funcreturn func_wrapperdef call_method(self, name=None):func = self.func_map.get(name, None)if func is None:raise Exception("No function registered against - " + str(name))return name, func()# 创建对象,用于添加方法进行调用app = MyApp()@app.register('/')def main_page_func():return "This is the main page."@app.register('/next_page')def next_page_func():return "This is the next page."print(app.call_method('/'))print(app.call_method('/next_page'))
func_wrapper:  / func_wrapper:  /next_page ('/', 'This is the main page.')('/next_page', 'This is the next page.')

ok,以上就是DE8UG给你带来的装饰器的相关知识了。你学会了吗?有任何问题,建议欢迎到「Python随身听」留言,感谢关注?。


看到这里的同学们有福利咯

无论你是点赞?,在看?,转发→,

还是留言?,赞赏?,

都会作为呈堂证供,

转化为后期DE8UG收费课程的相应积分,

无上限赠送

感谢你对Python随身听的支持

想查看更多?

请点击原文⬇️

python class用法理解_通过钢铁侠变身快速理解Python的装饰器用法相关推荐

  1. python装饰器用法_深入浅出分析Python装饰器用法

    本文实例讲述了Python装饰器用法.分享给大家供大家参考,具体如下: 用类作为装饰器 示例一 最初代码: class bol(object): def __init__(self, func): s ...

  2. python 装饰器 参数-python函数装饰器之带参数的函数和带参数的装饰器用法示例...

    本文实例讲述了python函数装饰器之带参数的函数和带参数的装饰器用法.分享给大家供大家参考,具体如下: 1. 函数带多个参数 # 普通的装饰器, 打印函数的运行时间 def decrator(fun ...

  3. python装饰器函数-python函数装饰器之带参数的函数和带参数的装饰器用法示例

    本文实例讲述了python函数装饰器之带参数的函数和带参数的装饰器用法.分享给大家供大家参考,具体如下: 1. 函数带多个参数 # 普通的装饰器, 打印函数的运行时间 def decrator(fun ...

  4. python装饰器实例-Python装饰器用法实例总结

    本文实例讲述了Python装饰器用法.分享给大家供大家参考,具体如下: 一.装饰器是什么 python的装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能, ...

  5. python装饰器 property_python中property和setter装饰器用法

    作用:调用方法改为调用对象, 比如 : p.set_name() 改为 p.set_name 区别:前者改变get方法,后者改变set方法 效果图: 代码: class Person: def __i ...

  6. python函数装饰器详解_Python语言函数装饰器用法实例详解

    这篇文章主要介绍了Python语言函数装饰器用法,以实例形式较为详细的分析了Python函数装饰器的常见使用技巧,需要的朋友可以参考下,希望对大家学习Python语言有所帮助. 本文实例讲述了pyth ...

  7. 21天python百度网盘_《21天学通Python》PDF 高清版百度网盘下载

    提取码:gr0z 内容简介  · · · · · · <21天学通Python>全面.系统.深入地讲解了Python编程基础语法与高级应用.在讲解过程中,通过大量实际操作的实例将Pytho ...

  8. python3.7用法_Python 3.7中dataclass装饰器用法详解

    Python 3.7的dataclass装饰器用法 Python 3.7新功能之dataclass装饰器详解 前言 Python 3.7 将于今年夏天发布,Python 3.7 中将会有许多新东西: ...

  9. python释放变量内存_看完2019年阿里巴巴Python面试题详解,月薪3万不是梦

    很多人想自学Python找工作,下面给大家分享一部分阿里巴巴的Python开发工程师的面试题目: 概念理解类题目: 1.请说一下你对迭代器和生成器的区别? 答:(1)迭代器是一个更抽象的概念,任何对象 ...

最新文章

  1. BIO  三位标注  (B-begin,I-inside,O-outside)
  2. [BX] 和 loop指令
  3. TensorFlow tf.data.Dataset
  4. java gc 可达性_JAVA--GC 垃圾回收机制----可达性分析算法
  5. c语言贪吃蛇源代码window32,Win32贪吃蛇源代码。背景非常简单
  6. 5999卖999!是噱头还是颠覆
  7. 【LeetCode】【数组】题号:414,第三大的数
  8. java客户端实验_java实验(客户端) 2015106宋世超
  9. TP5的类似TP3使用‘DEFAULT_THEME’的配置修改主题风格的方法,以及常见模板错误...
  10. 《数字图像处理》实验7
  11. c++使用librdkafka kerberos认证
  12. 在 linux ubuntu 18.04 上运行QQ音乐
  13. Git使用教程之初级入门命令行(二)
  14. android 虚拟按键自定义,Android手机底部栏虚拟按键的操作
  15. Ubuntu的ldconfig详解(解决*.so不是符号连接)
  16. 宝宝巴士儿歌下载链接
  17. 【修真院小课堂】JWT简单介绍
  18. 论Web Service 相关技术
  19. 软考不通过能不能补考?解答来了
  20. android bilibili弹幕技术解析,bilibili弹幕定位

热门文章

  1. php 环境优化,Nginx与PHP-fpm环境在大流量下的优化配置
  2. java split 路径,JAVA通过文件路径分隔符分割文件路径
  3. java 参数内存释放_JNI创建变量和释放变量
  4. php 处理tiff,TIFF图像文件(五):LZW的PHP应用
  5. java 特殊字符过滤器_java处理url中的特殊字符
  6. thymeleaf中的条件判断用法
  7. 改变support中AlertDialog的样式
  8. oracle怎么变为整数,如何在Oracle 11g SQL中为char添加整数?(How to add integers to char in Oracle 11g SQL?)...
  9. 中国2008经济数据
  10. jQuery-处理元素内容、表单元素