python 闭包

原文地址:https://blog.csdn.net/sc_lilei/article/details/80464645#commentBox

首先给出闭包函数的必要条件:

闭包函数必须返回一个函数对象
闭包函数返回的那个函数必须引用外部变量(一般不能是全局变量),而返回的那个函数内部不一定要return
几个典型的闭包例子:

# ENV>>> Python 3.6
    # NO.1
    def line_conf(a, b):
        def line(x):
            return a * x + b
        return line
    
    # NO.2
    def line_conf():
        a = 1
        b = 2
 
        def line(x):
            print(a * x + b)
        return line
 
    # NO.3
    def _line_(a,b):
        def line_c(c):
            def line(x):
                return a*(x**2)+b*x+c
            return line
        return line_c
正文:

    通过前面的例子相信你能够初步理解闭包的三个必要条件了。一脸懵逼?没关系,下面从python中函数的作用域开始讲起,一步步地的理解闭包。

    一、函数中的作用域

           Python中函数的作用域由def关键字界定,函数内的代码访问变量的方式是从其所在层级由内向外的,如“热身”中的第一段代码:

    def line_conf(a, b):
        def line(x):
            return a * x + b
        return line
           嵌套函数line中的代码访问了a和b变量,line本身函数体内并不存在这两个变量,所以会逐级向外查找,往上走一层就找到了来自主函数line_conf传递的a, b。若往外直至全局作用域都查找不到的话代码会抛异常。

            注意:不管主函数line_conf下面嵌套了多少个函数,这些函数都在其作用域内,都可以在line_conf作用域内被调用。

            思考上面这段代码实现了什么功能?

    #定义两条直线
    line_A = line_conf(2, 1) #y=2x+b
    line_B = line_conf(3, 2) #y=3x+2
    
    #打印x对应y的值
    print(line_A(1)) #3
    print(line_B(1)) #5
            是否感觉“哎哟,有点意思~”,更有意思的在后面呢。

            现在不使用闭包,看看需要多少行代码实现这个功能:

    def line_A(x):
        return 2*x+1
    def line_B(x):
        return 3*x+2
    
    print(line_A(1)) #3
    print(line_B(1)) #5
            不包括print语句的代码是4行,闭包写法是6行,看起来有点不对劲啊?怎么闭包实现需要的代码量还多呢?别急,我现在有个需求:

            再定义100条直线!

            那么现在谁的代码量更少呢?很明显这个是可以简单计算出来的,采用闭包的方式添加一条直线只需要加一行代码,而普通做法需要添两行代码,定义100条直线两种做法的代码量差为:100+6 -(100*2+4) = -98。需要注意的是,实际环境中定义的单个函数的代码量多达几十上百行,这时候闭包的作用就显现出来了,没错,大大提高了代码的可复用性!

            注意:闭包函数引用的外部变量不一定就是其父函数的参数,也可以是父函数作用域内的任意变量,如“热身”中的第二段代码:

    def line_conf():
        a = 1
        b = 2
 
        def line(x):
            print(a * x + b)
        return line
    二、如何显式地查看“闭包”

            接上面的代码块:

    L = line_conf()
    print(line_conf().__closure__) #(<cell at 0x05BE3530: int object at 0x1DA2D1D0>,
    # <cell at 0x05C4DDD0: int object at 0x1DA2D1E0>)
    for i in line_conf().__closure__: #打印引用的外部变量值
        print(i.cell_contents) #1  ; #2
            __closure__属性返回的是一个元组对象,包含了闭包引用的外部变量。

          ·  若主函数内的闭包不引用外部变量,就不存在闭包,主函数的_closure__属性永远为None:

    def line_conf():
        a = 1
        b = 2
        def line(x):
            print(x+1)  #<<<------
        return line
    L = line_conf()
    print(line_conf().__closure__) # None
    for i in line_conf().__closure__: #抛出异常
        print(i.cell_contents)
           ·  若主函数没有return子函数,就不存在闭包,主函数不存在_closure__属性:

    def line_conf():
        a = 1
        b = 2
        def line(x):
            print(a*x+b)
        return a+b #<<<------
    L = line_conf()
    print(line_conf().__closure__) # 抛出异常    
 

    三、为何叫闭包?

            先看代码:

    def line_conf(a):
        b=1
        def line(x):
            return a * x + b
        return line
 
    line_A = line_conf(2)
    b=20
    print(line_A(1))  # 3
              如你所见,line_A对象作为line_conf返回的闭包对象,它引用了line_conf下的变量b=1,在print时,全局作用域下定义了新的b变量指向20,最终结果仍然引用的line_conf内的b。这是因为,闭包作为对象被返回时,它的引用变量就已经确定(已经保存在它的__closure__属性中),不会再被修改。

               是的,闭包在被返回时,它的所有变量就已经固定,形成了一个封闭的对象,这个对象包含了其引用的所有外部、内部变量和表达式。当然,闭包的参数例外。

    四、闭包可以保存运行环境

               思考下面的代码会输出什么?

    _list = []
    for i in range(3):
        def func(a):
            return i+a
        _list.append(func)
    for f in _list:
        print(f(1))
                1 , 2,  3吗?如果不是又该是什么呢?    结果是3, 3, 3 。
              因为,在Python中,循环体内定义的函数是无法保存循环执行过程中的不停变化的外部变量的,即普通函数无法保存运行环境!想要让上面的代码输出1, 2, 3并不难,“术业有专攻”,这种事情该让闭包来:

    _list = []
    for i in range(3):
        def func(i):
            def f_closure(a):  # <<<---
                return i + a
            return f_closure
        _list.append(func(i))  # <<<---
        
    for f in _list:
        print(f(1))
    五、闭包的实际应用

                现在你已经逐渐领悟“闭包”了,趁热打铁,再来一个小例子:

    def who(name):
        def do(what):
            print(name, 'say:', what)
 
        return do
 
    lucy = who('lucy')
    john = who('john')
 
    lucy('i want drink!')
    lucy('i want eat !')
    lucy('i want play !')
    
    john('i want play basketball')
    john('i want to sleep with U,do U?')
 
    lucy("you just like a fool, but i got you!")
                看到这里,你也可以试着自己写出一个简单的闭包函数。

                OK,现在来看一个真正在实际环境中会用到的案例:

                1、【闭包实现快速给不同项目记录日志】

    import logging
    def log_header(logger_name):
        logging.basicConfig(level=logging.DEBUG, format='%(asctime)s [%(name)s] %(levelname)s  %(message)s',
                            datefmt='%Y-%m-%d %H:%M:%S')
        logger = logging.getLogger(logger_name)
 
        def _logging(something,level):
            if level == 'debug':
                logger.debug(something)
            elif level == 'warning':
                logger.warning(something)
            elif level == 'error':
                logger.error(something)
            else:
                raise Exception("I dont know what you want to do?" )
        return _logging
 
    project_1_logging = log_header('project_1')
 
    project_2_logging = log_header('project_2')
 
    def project_1():
 
        #do something
        project_1_logging('this is a debug info','debug')
        #do something
        project_1_logging('this is a warning info','warning')
        # do something
        project_1_logging('this is a error info','error')
 
    def project_2():
 
        # do something
        project_2_logging('this is a debug info','debug')
        # do something
        project_2_logging('this is a warning info','warning')
        # do something
        project_2_logging('this is a critical info','error')
 
    project_1()
    project_2()
#输出
2018-05-26 22:56:23 [project_1] DEBUG  this is a debug info
2018-05-26 22:56:23 [project_1] WARNING  this is a warning info
2018-05-26 22:56:23 [project_1] ERROR  this is a error info
2018-05-26 22:56:23 [project_2] DEBUG  this is a debug info
2018-05-26 22:56:23 [project_2] WARNING  this is a warning info
2018-05-26 22:56:23 [project_2] ERROR  this is a critical info
                这段代码实现了给不同项目logging的功能,只需在你想要logging的位置添加一行代码即可。

扩展: python中的装饰器特性就是利用闭包实现的,只不过用了@作为语法糖,使写法更简洁。如果掌握了闭包,接下来就去看一下装饰器,也会很快掌握的。
--------------------- 
作者:chaseSpace-L 
来源:CSDN 
原文:https://blog.csdn.net/sc_lilei/article/details/80464645 
版权声明:本文为博主原创文章,转载请附上博文链接!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值