python 嵌套函数->闭包->装饰器

概述

python的终极使用是装饰器,但在使用装饰器前需要掌握闭包,但是掌握闭包之前需要掌握嵌套函数,下面由浅入深来说下他们的关系。

嵌套函数

很多高级语言都支持函数的嵌套的定义(划重点:是定义,不是调用)。嵌套定义指在函数内部定义另一个函数, 该嵌套函数的作用域只能在父函数范围内。

def printMsg():

    print("hello world")
    def printMsg2("goog night xm"):
        print("")

    printer()

上述即为简单的嵌套函数,对于嵌套函数,它可以访问到外层作用域中的变量(专业术语叫:非局部变量 nonlocal,也称之为自由变量。)。示例如下:

def printMsg():
    msg = "hello xm"
    def printer():
        print(msg)

    printer()

闭包

简单来说就是 你调用了一个函数A ,然后函数A给你返回了函数B给你。这个返回的函数B就叫做闭包(嵌套函数在父函数内部就直接将得到结果。而闭包是返回子函数)。你在调用函数A的时候传递的参数就是自由变量。

def func(name):
    def inner_func(age):
        print 'name:', name, 'age:', age
    return inner_func

bb = func('the5fire')
bb(26)  
# >>> name: the5fire age: 26

这里面调用func的时候就产生了一个闭包——inner_func,并且该闭包持有自由变量——name,因此这也意味着,当函数func的生命周期结束之后,name这个变量依然存在,因为它被闭包引用了,所以不会被回收。

另外再说一点,闭包并不是Python中特有的概念,所有把函数做为一等公民的语言均有闭包的概念。不过像Java这样以class为一等公民的语言中也可以使用闭包,只是它得用类或接口来实现。

  • nonlocal 语句
    在 python 的函数内,可以直接引用外部变量,但不能改写外部变量,因此如果在闭包中直接改写父函数的变量,就会发生错误:
def cnt(param):
    dount = 0

    def couter():
        count += 1
        print("I'm", param, "No.", str(count))

    return counter
dobi = cnt("dobi")
dobi()
dobi()
dobi()

#UnboundLocalError:local variable 'count' referenced before assignment

在 python 2 中可以在函数内使用 global 语句,但全局变量在任何语言中都不被提倡,因为它很难控制,python 3 中引入了 nonlocal 语句解决了这个问题:

def cnt(param):
    dount = 0

    def couter():
        nonlocal count
        count += 1
        print("I'm", param, "No.", str(count))

    return counter
dobi = cnt("dobi")
dobi()
dobi()
dobi()

#I'm, dobi No.1
#I'm, dobi No.2
#I'm, dobi No.3

Nonlocal 与 global 的区别在于 nonlocal 语句会去搜寻本地变量与全局变量之间的变量,其会优先寻找层级关系与闭包作用域最近的外部变量。

装饰器

装饰器就是一种闭包的应用(对原有功能升级,而省略了闭包的的调用过程),只不过其传递的是函数:

def makebold(fn):
    def wrapped():
        return "<b>" + fn() + "</b>"
    return wrapped

def makeitalic(fn):
    def wrapped():
        return "<i>" + fn() + "</i>"
    return wrapped
@makebold # 等同于 hello = makebold(makeitalic(hello))
@makeitalic  #等同于 hello = makeitalic(hello)
def hello():
    return "hello xm"

print(hello())  
# 上述代码执行结果: <b><i>hello xm</i></b>

@makeitalic 装饰器将函数 hello 传递给函数 makeitalic,函数 makeitalic 执行完毕后返回被包装后的 hello 函数,而这个过程其实就是通过闭包实现的。@makebold 也是如此,只不过其传递的是 @makeitalic 装饰过的 hello 函数,因此最后的执行结果 外层,这个功能如果不用装饰器,其实就是显式的使用闭包:

def makebold(fn):
    def wrapped():
        return "<b>" + fn() + "</b>"
    return wrapped

def makeitalic(fn):
    def wrapped():
        return "<i>" + fn() + "</i>"
    return wrapped
@makebold # 等同于 hello = makebold(makeitalic(hello))
@makeitalic  #等同于 hello = makeitalic(hello)
def hello():
    return "hello xm"

hello = makeitalic(hello)
hello = makebold(hello)
print(hello())  

#<b><i>hello xm</i></b>

再说闭包

闭包的最大特点是可以将父函数的变量与内部函数绑定,并返回绑定变量后的函数(也即闭包),此时即便生成闭包的环境(父函数)已经释放,闭包仍然存在,这个过程很像类(父函数)生成实例(闭包),不同的是父函数只在调用时执行,执行完毕后其环境就会释放,而类则在文件执行时创建,一般程序执行完毕后作用域才释放,因此对一些需要重用的功能且不足以定义为类的行为,使用闭包会比使用类占用更少的资源,且更轻巧灵活,现举一例:假设我们仅仅想打印出各类动物的叫声,分别以类和闭包来实现:

class Animal(object):
    def __init__(self, animal):
        self.animal = animal

    def sound(self, voice):
        print(self.animal, ":", voice, "...")
dog = Animal("dog")
dog.sound("wangwang")
dog.sound("wowo")

# dog: wangwang ...
# dog: wowo ...
def voice(animal):
    def sound(voc):
        print(animal, ":",  voc, "...")
    return sound

dog = voice("dog")
dog.sound("wangwang")
dog.sound("wowo")

# dog: wangwang ...
# dog: wowo ...

可以看到输出结果是完全一样的,但显然类的实现相对繁琐,且这里只是想输出一下动物的叫声,定义一个 Animal 类未免小题大做,而且 voice 函数在执行完毕后,其作用域就已经释放,但 Animal 类及其实例 dog 的相应属性却一直贮存在内存中:

而这种占用对于实现该功能后,则是没有必要的。

除此之外,闭包还有很多其他功能,比如用于封装等,另外,闭包有效的减少了函数参数的数目,这对并行计算非常有价值,比如可以让每台电脑负责一个函数,然后串起来,实现流水化的作业等。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值