python闭包与装饰器有啥关系_python中闭包与装饰器解析

webchat.jpg

在开启本文的内容介绍时,我们先来了解下什么是函数式编程。所谓的函数式编程其实是一种编程范式,用人话就是写代码所遵循的一种方法或模式。简单地说,当某个函数(可称高阶函数)可以接受函数对象当做输入参数和返回值,这样一种写法即函数式编程。那么本文的闭包则应用了函数式编程的思想,而装饰器可以说是闭包的应用之一。

闭包

作用域

作用域是程序运行时变量可被访问的范围,故有全局变量和局部变量之说。一般我们把定义在函数之外的变量称之为全局变量,其作用域为当前文件或者当前类下。而定义在函数体内的局部变量称之为局部变量。而在python中函数可以嵌套,因而作用域的范围也有了明显的层级关系。即作用域最外层>第一层函数>第二层函数>…第n层函数,有相对的访问限制。我们来看个例子。

8992ce25gy1fpayu1fudij20u104daa7.jpg

什么是闭包

我们先从一个例子来看看。

8992ce25gy1fpaznm1msmj20u503q74d.jpg

这段代码最终的输出结果是python。按照一般的生命周期,变量name会随着function()的调用而移出内存。然而当你利用function()的返回函数wrapper再次调用时,仍旧返回了变量name的值,那也就说它并没有离开内存。这其实就是闭包的作用,它使得局部变量在函数外被访问成为可能。

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

在定义中的关键信息就是自由变量和函数(具体所指见上图),简单地说,该自由变量和函数一同存在于一个封闭的环境即函数所在的作用域内,同生共死。

总之,闭包本质上是一个函数,它包括了自由变量和函数两大部分,闭包使得这些变量的值保存在内存中。

闭包中的难点

8992ce25gy1fpb7mq94kkj20u805lt8p.jpg

首先可以自己先想一下如果是你,你会给出什么样的答案?? 如果是 0,2,4,6 那么请往下看。

首先我们定义了一个函数 fun() 并且是以列表生成式的方式返回结果,并且列表中的元素是由匿名函数lambda定义的。

为什么答案都是 6 呢?其实这是由于闭包的后期绑定导致的late binding,意思就是说在闭包中的自由变量是在内部函数被调用时被查找的,而随着 for 循环的执行,i 的值已经被更新到了3,即在闭包的封闭环境中自由变量 i 每次被查找都是拿到3的结果,再乘以 2,自然每次结果只能都是 6 。

那如果不想得到这样的结果,如何改写? 下面提供两种优雅的写法。

8992ce25gy1fpb84qewo6j20u3060dfz.jpg

装饰器

简单的装饰器

8992ce25gy1fpb0bx66boj20u805xt9d.jpg

decorator 就代表了一个装饰器,它用于给 log 函数增加额外的功能。实际的装饰过程定义在嵌套函数内部,在函数内部,log函数对象 以 func 参数来表示。那么这样一看就好像log被decorator 装饰了一样。

语法糖

在python,能在代码运行期间动态增加功能的方式即借助@符号。而这和Java 中的注解差不多一个意思。那么我们就可以用它来简化装饰器的调用过程。

8992ce25gy1fpb0x1xv3hj20u005t0t9.jpg

如上所示,@语法自动帮我们调用了decorator(log),省去了一次手动调用的过程,现在只需要调用一次log(),即可得到装饰效果,可谓是便民操作。而且如果还有其他函数也想被该装饰器装饰,同样加上个@decorator即可。这样其实也有一种封装代码的思想在里面。

带参数的装饰器

装饰器本质上亦是函数,那么就会有传入参数的需求。比如根据level 级别做不同的装饰操作,如此依赖也提高了装饰器的扩展性。

8992ce25gy1fpb5qb8lspj20tz08o3yz.jpg

如上所示,我们给装饰器函数user_logging设置了一个参数,这样当你指定不同的level 时,log 就会输出不同的结果。在此程序中调用log("hrx") 等价于调用wrapper=user_logging(decorator(log)) ,然后再调用wrapper("hrx"),这样其实比前述例子多了一层函数调用(因为多嵌套了一个函数)。

注:我们使用*arg,**kw作为 wrapper 函数的参数,这样设置之后表示此函数可以接受任意位置参数和关键字参数,这样一来 log 函数就可以指定各种各样的参数。

类装饰器

函数有装饰器,其实类也有装饰器,思想一样,但写法不太一样。其中实现装饰的步骤主要在类中的__call__ () 方法中进行。

8992ce25gy1fpb6bwaarlj20u00ap0t1.jpg

装饰器的缺点

在以上例子中,装饰器的实质都是log=wrapper (表面调用的是log ,但实际上调用了wrapper 执行),所以如果此时输入log.__name__得到的将会是wrapper的名字。可以使用wrapper__name__=log__name__,或利用python 内置装饰器functiontools.wrap实现。wrap操作可以将原函数的元信息拷贝到装饰器函数中的func内。所以完整的装饰器函数还应该加上functiontools.wrap(func)。

8992ce25gy1fpb75si22kj20u20bwgmg.jpg

总结

记住,python 中一切皆为对象,而函数算个特殊的对象吧。在闭包中,主要由函数对象与自由变量,加上其所处的封闭环境组成。再来,利用闭包,又得到了装饰器的用法,最后既扩展的函数的功能,又方便了代码的编写。

参考

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值