python装饰器
1、reduce是python中的内建函数,它是一个二元操作函数,他用来将一个数据集合中的所有数据进行下列操作:用传给reduce中的函数 func()(必须是一个二元操作函数)先对集合中的第1,2个数据进行操作,得到的结果再与第三个数据用func()函数运算,最后得到一个结果。
2、callable是检查对象object是否可调用。如果返回True,object仍然可能调用失败;但如果返回False,调用对象ojbect绝对不会成功。
3、lambda通常是在需要一个函数,但是又不想费神去命名一个函数的场合下使用,也就是指匿名函数。它只是一个表达式,而def则是一个语句。lambda表达式运行起来像一个函数,当被调用时创建一个框架对象。起到一个函数速写的作用,允许在代码内嵌入一个函数的定义。
下面开始正式开始学习装饰器,学习装饰器的第一个要理解的是比较基础的一个概念,用python的都是知道了,但是这里还是提一下,python中一切皆是对象,所以函数也一定是一个对象,被函数名所引用,这样函数名就可以被作为一个参数赋给其他变量,或者作为参数传给其他函数,比如:
func作为一个参数传给了para,就相当于给func起了一个别名,执行para就相当与执行func函数,可以看到打印的值就是func的值,将函数作为参数传给另一个参数也是一样的,如下:
第二个需要理解的可能有些人并不很清楚,那就是闭包原则,所谓闭包,就是将组成函数的语句和这些语句的执行环境打包在一起时,得到的对象,为了理解这个概念,先来看一个例子:
我们是调用的call.py的方法,但是打印输出确是dec.py文件定义的参数内容,这就涉及到解释器查找变量的顺序了,首先查找本地函数内部,然后查找上层,再上层,一直到最外层,再查找全局变量,再查找内置模块,在这期间,如果一旦查找到了该参数,就会终止查询,所以上面的代码会现在本文件逐层向上进行查询,查到了就是dec.py的参数,如果换一种调用方式就不同了,如下:
到这里,就可以提出闭包的作用了,闭包所要做的事就是封存上下文的环境,使之后的执行仍然可以使用闭包内的内容,上面的代码,当将called传入call函数时,called已经将执行环境封存了,在call内部再调用called的函数时called函数取的其实是被封存的执行函数的参数。
装饰器就用到了闭包的概念,看一下下面这个函数:
我们都知道,一旦一个函数执行结束,局部变量就会被回收,test_sum=dec()这一行执行完之后,按道理num这个变量就会被回收掉了,但是为什么执行test_sum()还会返回5050?这个其实就是在返回sum对象时已经成为了一个闭包,上下文已经被封存了,所以我们在执行test_sum函数时还可以取得上下文。
然后来说下装饰器,装饰器可以对一个函数、方法或者类进行加工,概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。较为经典的有插入日志、性能测试、事务处理等。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量函数中与函数功能本身无关的雷同代码并继续重用,比如,我们希望在一个函数执行前后打印执行开始和执行完成,依赖上面的对象可以作为参数传入的前提,那个上面的函数可以改为这样。
可以看到执行test_sum=dec(sum)会返回一个sum_log对象,并形成一个闭包,所以下面在执行test_sum(1,101)时func仍然是之前传入的sum函数,这就像在原来的函数基础上增加了一层装饰,但是这样写是不是有些麻烦,于是python提供了一种更优雅的写法,被称为语法糖,就是用@这个符号:
这样看起来是不是简单优雅多了,虽然这种写法第一眼看上去不理解,但是只要搞清楚它的实现原理就不难了,它仍然是执行的我们上面讲到的操作,即sum=dec(sum),sum(1,101)。
可能有人会问,既然装饰器也是函数,那么我可以为装饰器传参数吗?答案一定是可以的。
看上面的代码dec做了两次返回,内部返回的是sum_log,主要是将sum_log函数的上下文做封装,返回dec_para对象是将dec_para的上下文做封装,这样,我们在执行sum()的时候就可以使用上下文的参数了。而执行sum(1,101)的时候其实就相当于执行sum=dec(str)(sum),
sum(1,101)一样,这样,装饰器我们能就能理解的已经很清楚了。
但是理解了装饰器以后,我们一定会有这样的疑问,在装饰之后,我们执行的还是原来的函数吗?sum是否还是sum函数?我们可以试一下,将函数的名字打出来:
看结果,返回的函数名是sum_log,这样有些时候会有问题,python提供了一个叫wraps的decorator来消除这样的副作用,所以,可以把上面的代码改成这样:
这下,返回的函数名就又是sum了。
那么有时候我们并不确定装饰器是否会传入参数,碰到这种情况该如何进行兼容呢?这里介绍一个函数叫callabe(func),这个函数会判断参数是否是可调用的,我们用这个来进行判断来兼容装饰器不确定传参的情况,代码可以改动为如下:
这样,我们定义的装饰器就同时支持传参与不传参两种情况了,这样运用就会更加灵活。