热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

装饰器python详解_python装饰器详解

Python的装饰器是面试的常客,因为其写法复杂多变,经常忘记什么地方应该写哪种参数,新手学习起来也经常一头雾水,不怕不怕&

Python的装饰器是面试的常客,因为其写法复杂多变,经常忘记什么地方应该写哪种参数,新手学习起来也经常一头雾水,不怕不怕,看了这一篇你对装饰器的各种用法就全明白了。废话不多说,直接进入主题!

不带参数的函数,不带参数的装饰器

我们先来写一个简单的装饰器,实现将函数运行前后的情况记录下来。

defdec1(func):print(func)def_wrap():print('before run')

r=func()print('after run')returnrreturn_wrap

@dec1deff1():print('call f1')

上面只是定义了两个函数,运行后发现竟然有输出:

仔细看看,原来是第一个print语句的输出。这说明装饰的函数还没有实际运行的时候,装饰器就运行过了,因为@dec1相当于单独的一个语句:

dec1(f1)

那我们来正式运行一下f1吧:

f1(1)

输出如下确实达到了预期的效果:

before run

call f1

after run

不带参数的函数,带空参数的装饰器

后面我们还想要给装饰器加上参数呢,先试试用这个方式调用会发生什么情况:

@dec1()

输出了错误:

Traceback (most recent call last) in

8 return_wrap9

---> 10@dec1()11 deff1():12 print('call f1')

TypeError: dec1() missing1 required positional argument: 'func'

它说dec1需要接受func这个参数才行,那我们改改,作为f2函数吧:

defdec2():def_wrap(func):print(func)print('before run')returnfuncreturn_wrap

@dec2()deff2():print('call f2')

f2()

这下可以了:

before run

call f2

可是这个结构和原来有点不同了,而且,after run要写在哪里呢?很愁人地又改了一版,把它叫做f2x吧,对比dec1,又多了一层函数dec2x_w,开始有点晕:

defdec2x_w():defdec2x(func):print(func)def_wrap():print('before run')

r=func()print('after run')returnrreturn_wrapreturndec2x

@dec2x_w()deff2x():print('call f2x')

f2x()

运行一下看看,确实是想要的:

before run

call f2x

after run

后面我们就不加before/after run了。

带参数的函数,不带参数的装饰器

函数f2x想要接受参数呢?我们把它叫做a吧,比较简单,不就是_wrap的参数吗,加上就是了,而且又回到了只有两层函数就可以实现了:

defdec3(func):print(func)def_wrap(param):print(param)

r=func(param)returnrreturn_wrap

@dec3deff3(a):print('call f3', a)

f3(1)#很争气地输出了结果:

1call f31

带参数的函数,带参数的装饰器

下面我们实现一个装饰器,传入一个整数,和函数传入的参数做个加法:

defdec4_w(d_param):print(d_param)defdec4(func):print(func)def_wrap(param):print(param)

r= func(param +d_param)returnrreturn_wrapreturndec4

@dec4_w(2)deff4(a):print('call f4', a)

f4(1)

输出1+2=3:2

1call f43从调用的装饰器往里看,注意这三层函数的形参,第一层是装饰器的参数,第二层是函数,第三层是函数的参数,很有规律的排列,先记一下这个规律(要考的)。带两个参数的也是一样的,接着写就可以了:defdec5_w(d_param_1, d_param_2):print(d_param_1, d_param_2)defdec5(func):print(func)def_wrap(param):print(param)

r= func(param + d_param_1 +d_param_2)returnrreturn_wrapreturndec5

@dec5_w(2, 3)deff5(a):print('call f5', a)

f5(1)

输出1+2+3=6:2 3

1call f56如果用不定数量的位置参数,就用*args作为形参吧:def dec6_w(*args):

d_param_1, d_param_2,=argsprint(d_param_1, d_param_2)defdec6(func):print(func)def _wrap(*args):

param=args[0]print(param)

r= func(param + d_param_1 +d_param_2)returnrreturn_wrapreturndec6

@dec6_w(2, 3)deff6(a):print('call f6', a)

f6(1)print(f6.__name__)

顺便输出了一下f6的函数名:2 3

1call f66_wrap

咦!怎么肥四!!!f6怎么是里面那个_wrap的名字呢?不怕不怕,functools提供了一个wraps装饰器专治各种不服(在装饰器里面放上另一个装饰器):from functools importwrapsdef dec7_w(*args):

d_param_1, d_param_2,=argsprint(d_param_1, d_param_2)defdec7(func):print(func)

@wraps(func)def _wrap(*args):

param=args[0]print(param)

r= func(param + d_param_1 +d_param_2)returnrreturn_wrapreturndec7

@dec7_w(2, 3)deff7(a):print('call f7', a)

f7(1)print(f7.__name__)

这下正常输出f7了:2 3

1call f76f7

装饰器类(带参数的函数,带参数的装饰器)

用函数做装饰器局限性太多了,用相同的调用方法,把函数f7改成类怎么样?emmm…改造工程有点大,直接看看成品吧:

from functools importwrapsclassdec8_c:def __init__(self, *args):

self.d_param_1, self.d_param_2,=argsprint(self.d_param_1, self.d_param_2)def __call__(self, func):print(func)

@wraps(func)def_wrap(param):print(param)

r= func(param + self.d_param_1 +self.d_param_2)returnrreturn_wrap

@dec8_c(2, 3)deff8(a):print('call f8', a)

f8(1)print(f8.__name__)

看看是不是实现了一样的效果:

2 3

1call f86f8

虽然使用了__call__,但这里的__init__不能省略(因为它需要知道参数个数),否则会出现这个错误:

Traceback (most recent call last) in

14 returndec815

---> 16 @dec8_c(2, 3)17 deff8(a):18 print('call f8', a)

TypeError: dec8_c() takes no arguments

同时还可以发现,__call__只需要两层函数了,去掉了第二层,直接把_wrap的函数体往上提了一层!

装饰器类(带参数的函数,不带参数的装饰器)

大概是吃饱了撑着,又想要实现一开始那个不带参数的装饰器了,那就继续敲敲打打一番看看:

classdec9_c:def __init__(self, func):print(func)

self.func=func

self.__name__ = func.__name__

def __call__(self, param):print(param)

func=self.func

r=func(param)returnr

@dec9_cdeff9(a):print('call f9', a)

f9(1)print(f9.__name__)

赶快运行看看:

1call f91f9

咦,f9的函数名可以直接打印,这下都不用@wraps了呢!呃,再仔细看看,这写法好像有些不一样啊:

dec8_c的init带的是装饰器的参数,可是dec9_c带的是装饰器函数自己!

所以实际调用的函数名也可以在init中传给它了哦!

而且call函数也简洁了很多,看来有没有参数真的有很大区别呢!

这里先做个总结,装饰器使用函数名形式(不带括号)和使用函数调用形式(带括号和参数)在实现上是不同的,因为前者是函数本身,而后者是从装饰器函数中返回的函数。这也是f2相比f1缺少了记录after run的原因,因为dec1直接调用了f2,而dec2先运行得到函数,再把函数返回去调用f2。用装饰器类就可以解决这个问题,因为它是对__call__的调用,只需要自己定义一下就可以了。

上面的f9要写两个函数,能不能写得和f1一样简洁?当然是可以的,使用__new__大法:

from functools importwrapsclassdec9x_c:def __new__(self, func):print(func)

@wraps(func)defdec9x(param):print(param)

r=func(param)returnrreturndec9x

@dec9x_cdeff9x(a):print('call f9x', a)

f9x(1)print(f9x.__name__)

这样就避开了函数调用,不用打call了(定义__call__函数),快看它来了:

1call f9x1f9x



推荐阅读
  • Linux重启网络命令实例及关机和重启示例教程
    本文介绍了Linux系统中重启网络命令的实例,以及使用不同方式关机和重启系统的示例教程。包括使用图形界面和控制台访问系统的方法,以及使用shutdown命令进行系统关机和重启的句法和用法。 ... [详细]
  • 开发笔记:加密&json&StringIO模块&BytesIO模块
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
  • 微软头条实习生分享深度学习自学指南
    本文介绍了一位微软头条实习生自学深度学习的经验分享,包括学习资源推荐、重要基础知识的学习要点等。作者强调了学好Python和数学基础的重要性,并提供了一些建议。 ... [详细]
  • Java太阳系小游戏分析和源码详解
    本文介绍了一个基于Java的太阳系小游戏的分析和源码详解。通过对面向对象的知识的学习和实践,作者实现了太阳系各行星绕太阳转的效果。文章详细介绍了游戏的设计思路和源码结构,包括工具类、常量、图片加载、面板等。通过这个小游戏的制作,读者可以巩固和应用所学的知识,如类的继承、方法的重载与重写、多态和封装等。 ... [详细]
  • 本文介绍了Python对Excel文件的读取方法,包括模块的安装和使用。通过安装xlrd、xlwt、xlutils、pyExcelerator等模块,可以实现对Excel文件的读取和处理。具体的读取方法包括打开excel文件、抓取所有sheet的名称、定位到指定的表单等。本文提供了两种定位表单的方式,并给出了相应的代码示例。 ... [详细]
  • YOLOv7基于自己的数据集从零构建模型完整训练、推理计算超详细教程
    本文介绍了关于人工智能、神经网络和深度学习的知识点,并提供了YOLOv7基于自己的数据集从零构建模型完整训练、推理计算的详细教程。文章还提到了郑州最低生活保障的话题。对于从事目标检测任务的人来说,YOLO是一个熟悉的模型。文章还提到了yolov4和yolov6的相关内容,以及选择模型的优化思路。 ... [详细]
  • 本文介绍了lua语言中闭包的特性及其在模式匹配、日期处理、编译和模块化等方面的应用。lua中的闭包是严格遵循词法定界的第一类值,函数可以作为变量自由传递,也可以作为参数传递给其他函数。这些特性使得lua语言具有极大的灵活性,为程序开发带来了便利。 ... [详细]
  • Linux服务器密码过期策略、登录次数限制、私钥登录等配置方法
    本文介绍了在Linux服务器上进行密码过期策略、登录次数限制、私钥登录等配置的方法。通过修改配置文件中的参数,可以设置密码的有效期、最小间隔时间、最小长度,并在密码过期前进行提示。同时还介绍了如何进行公钥登录和修改默认账户用户名的操作。详细步骤和注意事项可参考本文内容。 ... [详细]
  • Spring源码解密之默认标签的解析方式分析
    本文分析了Spring源码解密中默认标签的解析方式。通过对命名空间的判断,区分默认命名空间和自定义命名空间,并采用不同的解析方式。其中,bean标签的解析最为复杂和重要。 ... [详细]
  • 本文介绍了设计师伊振华受邀参与沈阳市智慧城市运行管理中心项目的整体设计,并以数字赋能和创新驱动高质量发展的理念,建设了集成、智慧、高效的一体化城市综合管理平台,促进了城市的数字化转型。该中心被称为当代城市的智能心脏,为沈阳市的智慧城市建设做出了重要贡献。 ... [详细]
  • 本文介绍了一个Java猜拳小游戏的代码,通过使用Scanner类获取用户输入的拳的数字,并随机生成计算机的拳,然后判断胜负。该游戏可以选择剪刀、石头、布三种拳,通过比较两者的拳来决定胜负。 ... [详细]
  • Commit1ced2a7433ea8937a1b260ea65d708f32ca7c95eintroduceda+Clonetraitboundtom ... [详细]
  • IB 物理真题解析:比潜热、理想气体的应用
    本文是对2017年IB物理试卷paper 2中一道涉及比潜热、理想气体和功率的大题进行解析。题目涉及液氧蒸发成氧气的过程,讲解了液氧和氧气分子的结构以及蒸发后分子之间的作用力变化。同时,文章也给出了解题技巧,建议根据得分点的数量来合理分配答题时间。最后,文章提供了答案解析,标注了每个得分点的位置。 ... [详细]
  • baresip android编译、运行教程1语音通话
    本文介绍了如何在安卓平台上编译和运行baresip android,包括下载相关的sdk和ndk,修改ndk路径和输出目录,以及创建一个c++的安卓工程并将目录考到cpp下。详细步骤可参考给出的链接和文档。 ... [详细]
  • 本文介绍了多因子选股模型在实际中的构建步骤,包括风险源分析、因子筛选和体系构建,并进行了模拟实证回测。在风险源分析中,从宏观、行业、公司和特殊因素四个角度分析了影响资产价格的因素。具体包括宏观经济运行和宏经济政策对证券市场的影响,以及行业类型、行业生命周期和行业政策对股票价格的影响。 ... [详细]
author-avatar
乐家巢欧美家具_636
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有