对python的闭包,装饰器的理解

先来看一段简单的代码

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的装饰器,要记住它被返回的是一个函数,是装饰器里面定义的另一个函数!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值