详解python中闭包和装饰器
-
首先来说一点python函数的一点知识:
当python遇到def语句的时候,会在内存中生成一个函数对象,并且这个对象考函数名来引用,但是这个函数体内部的语句只有在函数的调用的时候才会被执行,而函数调用结束了,就是函数返回时,其内部所有所生成的数据所有都会被销毁
-
闭合函数:
我们的所有函数在调用返回时,内层函数记忆外层函数中的变量,但是一定是内层函数调用的外层函数的变量,如果内层函数不会调用,那么这个记忆就是不存在的。
-
通过例子来看看,闭合函数是怎么样发生功能的
我们来看个例子:
[email protected]:~$ python3
Python 3.5.2 (default, Sep 14 2017, 22:51:06)
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> def f1(x):
... def f2(y):
... return x*y
... return f2
...
解释:在这里定义了一个函数f1,然后在里面嵌套一个函数f2,函数f2返回两个函数的参数之积,在这里注意的是,外层函数f1把整个内层函数f2当作返回值给予返回。
>>> f1(8)
<function f1.<locals>.f2 at 0x7fcafd59ea60>
>>> f3 = f1(8)
>>> type(f3)
<class 'function'>
>>> f3(7)
56
>>> f3(8)
64
解释:f1函数需要一个参数,f1(8)返回的是一个函数,为了这个函数能够调用,我们应该把这个函数的地址给引用一下,例如f3(7)这样就可以调用了。
Python 闭包:我们从上面可以看出,f1主要是为f2来提供运行环境的,一个主要目的就是为f2提供一些。那么就可以这么定义,闭包是指函数及相关的环境,所组成的整体【那个内部的才是真正的函数,外部的叫做环境】但是闭包本身并不是函数,它只是形式上像函数。
函数在嵌套环境中,如果在一个内层函数里,对外层函数作用域中的变量做了引用,那么在外层函数返回后,内层函数依然可以使用外层函数中的变量,这种变量就构成了内层函数可以使用时的环境。所以闭包对于函数隐藏和函数之间随意切换可以发挥作用,
>>> f3(10)
80
>>> f3 = f1(5)
>>> f3(5)
25
>>> f3(7)
35
>>>
解释:我们还可以改变f3函数的值,在进行计算这都是可以的。
4、适用于情况:
例如我们的棋盘,棋子在走的时候,都是相对于当前的位置,向左右或者是向前移动,这就满足python的闭包特性。
像上面的就是一个棋盘的设计方式,比如起始地位置是(10,10), action(1,2)就是x轴移动一个单位,y轴移动两个单位。然后就会产生新的位置,我们继续走的话就把新的位置赋值给action就行了。悔棋的话就是变成相反数就可以了。
这其实就是闭合函数的一种调用方式,最明显的特性我们可以看出,我们只需要对外层函数施加一个变数,那么内层就必须要随着这个变数的变化而改变,所以就是给内层环境创造了一个执行环境,这就是闭包。
5、装饰器
所谓装饰器就是在闭包的基础上传递了一个函数,然后覆盖原来函数的执行入口,以后调用这个函数的时候,就可以额外实现一些功能了。装饰器的存在主要是为了不修改原函数的代码,也不修改其他调用这个函数的代码,就能实现功能的拓展。
装饰器本质上就是一个python函数,返回值也是一个函数对象,装饰器是一个很著名的设计模式,经常被用于有切面需求的场景,较为经典的有插入日志、性能测试、事务处理等。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量函数中与函数功能本身无关的雷同代码并继续重用。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。
5.1、举个简单的例子来看看装饰器:
[email protected]:~$ vim test.py
#!/usr/bin/python
#装饰器开始
def add_count(func):
def wrapper():
print ("using func .......")
func()
return wrapper
#装饰器结束
def show_haha():
print ("haha")
show_haha = add_count(show_haha)
解释:这个show_haha在里面这个命名空间里面就是func,就是因为有闭包的原因,所以我们在里面的wrapper这个函数就可以看到func这个函数,并且返回的这个wrapper就是把func包装,并且升级过的一个东西。最后面的这个我们得到的这个新的show_haha其实就是wrapper
5.1再看一个把日志输出到界面的例子:
1 2 3 4 5 |
def logger(func): def inner(*args, **kwargs): #1 print "Arguments were: %s, %s" % (args, kwargs) return func(*args, **kwargs) #2 return inner |
请注意我们的函数inner,它能够接受任意数量和类型的参数并把它们传递给被包装的方法,这让我们能够用这个装饰器来装饰任何方法。
Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
@logger def foo1(x, y=1): return x * y @logger def foo2(): return 2 foo1(5, 4) Arguments were: (5, 4), {} 20 foo1(1) Arguments were: (1,), {} 1 foo2() Arguments were: (), {} 2 |
随便调用我们定义的哪个方法,相应的日志也会打印到输出窗口,和我们预期的一样。