先来看一段简单的代码
def add():
a = 2
b = 5
def func(c):
return a*c+b
return func
在函数func里面用到了外面的参数a, b(称为函数的环境变量)。像这样,函数与其环境变量就构成了闭包。简单地理解呢,就是嵌套函数。
通常地,函数被调用之后其命名空间随着函数的结束而结束,但是在闭包中不然,外部函数的命名空间和内部的结合到了一起。
当我们调用外层函数时,返回内层函数,特别注意!!返回的是一个函数。
续上面的代码
a = add()
print a
# <function func at 0xb70c9f7c>
a(5) # output: 15
可见调用add后,返回func函数,我们让a指向了这个新函数,直接print的话并没有结果,唯有我们再次调用的时候才计算。
下面我们通过一个例子来了解一下闭包使用时的注意事项
def count():
fs = []
for i in range(1, 4):
def f():
return i * i
fs.append(f)
return fs
f1, f2, f3 = count()
print f1, f2, f3 # 9 9 9
调用count()的时候便返回三个函数,都放在fs的列表里,最后由f1,f2,f3分别指向。但是为什么我们打印出来的时候不是:1,4,9而是9,9,9呢。
这就是我们使用闭包时要注意的地方了!!返回来的是函数而不是计算结果,而其中的i值仍然在自增,当我们打印的时候,i已经是3了,我们调用f1,f2,f3的时候才真正进行了运算,这时运算的数据是3。所以,当我们使用闭包的时候要注意内部函数不要使用循环变量。
如果一定要用的话,也不是没有办法,再加一个函数“记住”变量值就好了。像这样
def count():
fs = []
for i in range(1, 4):
def f(j):
def g():
return j * j
return g
fs.append(f(i))
return fs
f1, f2, f3 = count()
这时候,f1,f2,f3对应的是被返回来的f(1), f(2), f(3)
好了,了解闭包后我们就可以涉及一下装饰器了。所谓装饰器就是实现一些小功能(比如在函数操作前后输出打印点什么),又能不让代码嵌入其中。通常我们用@语法把装饰器,就是实现小功能的函数放在另一个函数的上一行。
例1
def log(func):
def wrapper(*args, **kw):
print 'call %s()' % func.func_name
return func(*args, **kw)
return wrapper
@log # now* = log(now)
def now():
now.func_name = "now"
print "2016-7-17"
now() # 调用的是now*
#call now
#2016-7-17
“调用的是now*”的意思是这时的now指向的是由log()函数返回来的wrapper()函数。下面的例子写一个三层循环,可以打印附加文字。
例2
def log(text):
def decorator(func):
def wrapper(*arg, **kw):
print '%s %s()' %(text, func.func_name)
return func(*arg, **kw)
return wrapper
return decorator
@log('execute')
def time():
time.func_name = "time"
print "time for now is 2016.7.17"
time()
#execute time
#2016.7.17
如果再加一点修改,可以把上面两个例子:例1和例2整合
calllable()判断是否可调用。显然,对象是函数的话返回true,字符串的话返回false
def log2(text): # text有可能是文本,也可能是函数
if callable(text): # if callable(text)=true then text 是函数
def wrapper(*args, **kw):
print 'begin call: ' + text.func_name
text(*args, **kw)
print 'end call'
return wrapper
else: # if false, text是文本,接下来是三层循环
def decorator(func):
def wrapper(*args, **kw):
print "begin call: " + text
func(*args, **kw)
print 'end call: '
return wrapper
return decorator
最后我们来看两个稍复杂的例子加深印象和理解:
下面例子中,我定义了全局变量base,但是com方法中又定义了,最后wrapper函数使用的会是重新定义了的这个base(=7)。当定义了一个函数时,函数内的变量构成该函数的名字空间,前面提到的:在闭包里面,内部函数的命名空间与外部函数结合到了一起,这也是一种将命名空间静态化的方法。
base = 2
def com(func):
base = 7
# print "enter com"
def wrapper(value):
if value > base:
print "wrapper " + str(base) # wrapper 7
return func(value)
else:
return func(base)
return wrapper
@com
def sub_ten_sqart(value):
return math.sqrt(value - 7)
print sub_ten_sqart(11) # 2.0
print sub_ten_sqart(5) # 0.0
你觉得下面这段代码的输出会是怎样的呢?
def demo_1(func):
print "enter demo_1"
def wrapper(a, b):
print "enter demo_1's wrapper"
func(a, b)
return wrapper
def demo_2(func):
print "enter demo_2"
def wrapper(a, b):
print "enter demo_2's wrapper"
func(a, b)
return wrapper
@demo_1
@demo_2
def add_func(a, b):
print "the result is %d" %(a+b)
add_func(1, 2)
输出是
"enter demo_2"
"enter demo_1"
"enter demo_1's wrapper"
"enter demo_2's wrapper"
"the result is 3"
你对了吗~
当被两个装饰器修饰时,从最里层开始往外应用。
也就是说,过程相当于是这样的:
add_func = demo_2(add_func)
打印输出”enter demo_1”并且此时add_func变成了 wrapper(add_func)函数,但是该wrapper里面还未执行。
紧接着:add_func = demo_1(add_func)=demo_1(wrappeer(add_func))
打印”enter demo_2”,传递给demo_1里面的wrapper()的add_func其实是来自demo_2的wrapper,所以在demo_1的wrapper里面,先打印”enter demo_1’s wrapper”,最后才执行了demo_2中的wrapper。
所以,对于Python的装饰器,要记住它被返回的是一个函数,是装饰器里面定义的另一个函数!