python中如何理解装饰器代码?

幻翠


先分解一下楼主提出的问题:

  1. 如何理解return一个函数,它与return一个值得用法区别在哪?
  2. 在wrapper函数中,为什么能返回一个在wrapper函数中没有定义的func函数?

在简单概括一下这两个问题涉及到的Python 知识点 :

  • 问题1:Python的函数对象,函数可以被赋值,函数可以作为参数传递,函数可以作为返回值。

  • 问题2:Python 的 闭包

接下来,我们根据实例,逐一的介绍一下:


函数对象

Python一切皆对象,函数这一语法结构也是一个对象。函数被称为第一类对象,函数可以被当做数据传递。在函数对象中,我们像使用一个普通对象一样使用函数对象,比如更改函数对象的名字,或者将函数对象作为参数进行传递。


函数可以被赋值

执行上述代码,输出如下,请留意代码中的注释信息。


函数可以作为参数传递

执行上述代码,输出如下,请留意代码中的注释信息。


函数可以作为返回值

如上示例中,当函数(不带括号)作为返回值时,返回的是函数的内存地址,代码执行顺序及结果,如下:

与上面代码不同的是,接下来我们尝试一下让fun_b返回 return fun(),多了一个括号,代码如下:

当 执行 return fun() 时,实际上是先调用fun_a函数,再将fun_a的返回结果作为fun_c的返回,运行代码,结果如下:



闭包

定义:在计算机科学中,闭包(Closure)是词法闭包(Lexical Closure)的简称,是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。

结合上面例子,一个闭包可以简单理解为调用了一个函数fun_a,这个函数fun_a返回了一个函数fun_b。这个返回的函数fun_b就叫做闭包。在调用函数fun_a的时候传递的参数a、c就是自由变量。

上面例子中,函数 fun_b 与环境变量 a,c 构成闭包。在创建闭包的时候,我们通过fun_a 的参数 a,c明确这两个环境变量的取值,因此确定了函数的最终形式(y = 2b + 10)。我们只需要变换参数a,b就可以获得不同的直线表达函数。由此,我们可以看到,闭包的引入提高代码了代码的可复用性,更加简洁。执行代码,输出结果如下:



修饰器

顾名思义,从字面意思可以理解为,它是用来"装饰"Python的工具,使得代码更具有Python简洁的风格。装饰器本质上是Python函数,能够实现让其他函数在不需要做任何代码变动的前提下增加额外功能。

可以看出,fun_a(fun_b)的执行过程如下:

  1. 执行函数fun_a,将fun_b当作参数传进去,fun_b()本身也是对象。
  2. 执行print (fun()) 代码时,先执行了 fun_b(),然后打印'Run Function B' , 返回 2019-06-11 21:17:27 。
  3. print(fun()) 打印了fun_b()的返回结果 2019-06-11 21:17:27 。

使用修饰器进行改造,如下:

执行fun_b相当于 fun_b = fun_a(fun_b) ,只是在定义fun_b时,在其前使用@fun_a 进行修饰。


再引入闭包进行改造,如下:

在fun_a内部的函数retry(),是如何获取fun这个参数来执行的?执行fun_a函数return的是retry这个函数,而retry并没有接受fun这个传参。这就是Python里的闭包的概念,闭包就是指运行时自带上下文(自由变量)的函数,如这里的retry函数,他运行的时候自带了上层函数fun_a传给他的fun这个参数,所以才可以在运行时对fun进行处理和输出。


修饰器实现重试机制

简单的重试机制实现


复杂的重试机制实现

支持重试次数和等待时间,如下:


进一步深入了解修饰器,可以阅读这篇文章:

https://www.toutiao.com/i6731320536732795405/

软件测试开发技术栈


首先,我们先理解一段简单的代码,从这段代码可以看出虽然都是foo函数,但是方法内部逻辑不同,输出结果也是不同的。同理,虽然带有装饰器的函数体看起来一样,但是加上@后内部逻辑已经不同,继续看下去就明白了。

现在有一个需求,有一个开发部门要搭基础服务,所有服务对外提供,例如数据库操作、API接口、Redis操作等。外部使用这些服务时直接调用该方法即可。

现在突然有一个紧急事件,要求开发部门在此基础上加验证功能,小B的测试代码如下

过了一个星期,又有很多新功能需要开发,又要加验证功能,小B终于坚持不住写这么重复代码,崩溃离职了。。由于小B的离职,小C重构了代码结构,写的测试代码如下:

老板看了看小C的代码,语重心长地对小C说:写代码要遵循开发封闭原则,虽然这个原则是面向对象开发,但是也适用于函数式编程,简单而言,它规定已经实现的功能代码不允许被修改,但可以被扩展。

  • 封闭:已实现的功能代码块
  • 开放:对扩展开发

老板说完后自己操手写了几行代码,而且用上了@符来表示对该函数的装饰器,其实就是对原函数的扩展,在不破坏原函数的基础上加新的功能。上面这段代码我简单的解释下,当f1函数加载@w1时,首先进行w1函数中的inner操作,程序经过一系列验证后返回f1函数,这时inner函数其实就是带有验证的f1,再返回inner就可以得到带有验证且f1值的结果。

以上就是对Python装饰器的一个简单例子的通俗解释,如果有什么疑惑或意见欢迎在评论区讨论交流。


如果你对学习人工智能和科技新闻感兴趣,可以订阅我的头条号,我会在这里发布所有与算法、机器学习以及深度学习有关的有趣文章。偶尔也回答有趣的问题,有问题可随时在评论区回复和讨论,看到即回。


大魔王Hacker


长文预警,【最浅显易懂的装饰器讲解】

能不能专业地复制题目?配上代码,问题分段。

我来给提主配上问题的代码。

正式回答:

1:如何理解return一个函数,它与return一个值得用法区别在哪?

敲黑板,"python中,一切都是对象"。

值是对象,函数也是对象。

上图,num是int类的实例对象,funcobj是function类的一个实例对象。

所以返回一个值和返回一个函数并没有什么不同,本质都是返回一个对象。

但是由于值类型和函数类型的使用方法不同,值直接使用,函数需要加上()调用。

2.在wrapper函数中,为什么能返回一个在wrapper函数中没有定义的func函数?

先更正你的提问,wrapper函数并没有返回func函数,而是返回func函数的运行结果。

因此,作为参数传递给wrapper函数之后,wrapper当然可以调用func函数。

3.怎么理解在log中作为参数存在的func,在wrapper函数中成了函数?

相信你已经明白用对象的眼光看待,因此和问题2其实是一个问题。

4.这对log函数本身的使用有哪些影响,或者说当A函数的参数是一个函数时,如何使用A函数?

什么是装饰器?装饰器就是装饰函数的!

问题图中的log函数就是为了在不更改func的情况下,每次调用func之前,都会执行

想到了什么?日志!没错!

那么,你可能会问,为什么不在func函数print日志呢?

问得实在太好了!

1:如果func函数是你写的,那么你当然可以这么做;如果不是你写的,你这么做试试?

比如在系统open函数的最前面加上print()....

2:如果你有n个函数,在执行的前后都会执行一些类似的代码。

以下是2个不同的写法

显然右边代码量更少,更容易维护,但是还有更好的写法。

请注意,不修改add函数和sub函数的情况下,就为这2个不同的函数的执行前后增加了新的功能。

把add函数和sub函数装饰得更强大了。

上述代码后半段仍有改进的空间。

看,经过@decorator装饰add和sub函数之后,使用时更方便了。

细心的朋友,相信已经注意到了add函数和sub函数的参数不一样的。

没有错,我是故意的。

奥妙在于*args 和**kwgs,可变参数。

上图是对指定参数、可变参数*args、可变**kwgs的示例。

对于装饰器来说,不需要指定参数,因此只需*args和**kwgs即可以表示。


分享到:


相關文章: