• Python进阶 三器一闭1.3闭包


    1. 目的

    想想看怎样用程序实现下面的功能呢?

    1. 有2个人在说话,说话的顺序可能不同
    2. 每次说话的时候,都要标记是谁说的话

    今天我们要研究的知识点是 “闭包”,实现上述功能的方式可能有多种,但是闭包会更简单

    2. 尝试解决上述问题

    2.1 尝试1(最普通的方式)

    def say(user_name, content):
        print("(%s):%s" % (user_name, content))
    
    
    user_name1 = "张三"
    user_name2 = "李四"
    
    say(user_name1, "你努力了吗?")
    say(user_name2, "为啥努力!")
    
    say(user_name1, "你确定不要努力吗?")
    say(user_name2, "嗯,确定?")
    
    say(user_name1, "那可就不要要怪别人努力了啊")
    say(user_name2, "别人与我何关!")
    
    say(user_name1, "隔壁那户人家姓xxxx")
    say(user_name2, "( ⊙ o ⊙ )啊!")
    

      

     

    运行效果如下:

    image-20190228195639649

    小总结:

    上述代码,已经实现了要求,但是不觉得麻烦吗?每次都要将用户的名字传递到say函数中,肯定有办法来解决这个问题,那你觉得是什么呢?

    2.2 尝试2(面向对象)

    class Person(object):
        def __init__(self, name):
            self.user_name = name
    
        def say(self, content):
            print("(%s):%s" % (self.user_name, content))
    
    
    p1 = Person("张三")
    p2 = Person("李四")
    
    p1.say("你努力了吗?")
    p2.say("为啥努力!")
    p1.say("你确定不要努力吗?")
    p2.say("嗯,确定?")
    p1.say("那可就不要要怪别人努力了啊")
    p2.say("别人与我何关!")
    p1.say("隔壁那户人家姓xxxx")
    p2.say("( ⊙ o ⊙ )啊!")
    

      

     

    运行效果:

    image-20190228200447021

    小总结:

    通过面向对象的方式能够实现上述要求,但是发现使用了类以及对象,总体感觉还是较为复杂,再者说继承的object类中有很多默认的方法,既然这个程序不需要,显然会造成一定的浪费

    是否有更简单的方式呢?

    2.3 尝试3(闭包)

    def person(name):
        def say(content):
            print("(%s):%s" % (name, content))
    
        return say
    
    p1 = person("张三")
    p2 = person("李四")
    
    p1("你努力了吗?")
    p2("为啥努力!")
    p1("你确定不要努力吗?")
    p2("嗯,确定?")
    p1("那可就不要要怪别人努力了啊")
    p2("别人与我何关!")
    p1("隔壁那户人家姓xxxx")
    p2("( ⊙ o ⊙ )啊!")
    

      

     

    运行效果:

    image-20190228201149248

    稍加完善代码:

    def who(name):
        def do(content):
            print("(%s):%s" % (name, content))
    
        return do
    
    
    zhangsan = who("张三")
    lisi = who("李四")
    
    zhangsan("你努力了吗?")
    lisi("为啥努力!")
    zhangsan("你确定不要努力吗?")
    lisi("嗯,确定?")
    zhangsan("那可就不要要怪别人努力了啊")
    lisi("别人与我何关!")
    zhangsan("隔壁那户人家姓xxxx")
    lisi("( ⊙ o ⊙ )啊!")
    

      

    image-20190228205206341

    估计第一次看到函数嵌套调用,你会很惊讶,不用着急,这就是我们今天的主角“闭包”

    3. 闭包

    3.1 引用

    # 定义函数可以理解为:
    # 定义了一个全局变量,其变量名字是函数的名字,即test
    # 这个test变量指向了一个代码块,这个代码块是函数
    # 其实就是说test保存了一个代码块的地址,即引用
    def test():
        print("--- in test func----")
    
    test()  # 这是调用函数
    
    ret = test # 用另外一个变量 复制了 test这个引用,导致ret变量也指向那个 函数代码块
    
    # 下面输出的2个地址信息是相同的
    print(id(ret))
    print(id(test))
    
    # 通过引用调用函数
    ret()
    

      

     

    运行结果:

    --- in test func----
    140212571149040
    140212571149040
    --- in test func----
    

      

     

    3.2 什么是闭包

    闭包(closure) 定义非常抽象,很难看懂

    下面尝试从概念上去理解一下闭包

    在一些语言中,在函数中可以(嵌套)定义另一个函数时,如果内部的函数引用了外部的函数的变量,则可能产生闭包。闭包可以用来在一个函数与一组“私有”变量之间创建关联关系。在给定函数被多次调用的过程中,这些私有变量能够保持其持久性。 —— 维基百科https://zh.wikipedia.org/wiki/闭包_(计算机科学)

    用比较容易懂的人话说:就是当某个函数被当成对象返回时,夹带了外部变量,就形成了一个闭包

    可以这样理解,闭包就是能够读取其他函数内部变量的函数

    看如下案例,便于理解什么是闭包

    def make_printer(msg):  # 可以认为是 外部函数
        def printer():  # 可以认为是 内部函数
            print(msg)
        return printer  # 返回的内部函数的引用
    
    printer = make_printer('Good!')
    printer()
    

      

     

    运行效果:

    image-20190228203436221

    4. 闭包案例

    4.1 案例1(简单计算数值)

    def test(number):
        def test_in(number_in):
            print("in test_in 函数, number_in is %d" % number_in)
            return number+number_in
        return test_in
    
    
    # 给test函数赋值,这个20就是给参数number
    ret = test(20)
    
    # 注意这里的100其实给参数number_in
    print(ret(100))
    
    # 注意这里的200其实给参数number_in
    print(ret(200))
    

      

     

    运行结果:

    image-20190228203705597

    4.2 案例2(计算坐标)

    def line_conf(a, b):
        def line(x):
            return a*x + b
        return line
    
    line1 = line_conf(1, 1)
    line2 = line_conf(4, 5)
    print(line1(5))
    print(line2(5))
    

      

     

    运行效果:

    image-20190228203816104

    这个例子中,函数line与变量a,b构成闭包。

    在创建闭包的时候,我们通过line_conf的参数a,b设置了这两个变量的取值,这样就确定了函数的最终形式(y = x + 1y = 4x + 5)。

    如果需要修改这条线的信息,只需要变换参数a,b,就可以获得不同的直线表达函数。

    由此,我们可以看到,闭包也具有提高代码可复用性的作用

    如果没有闭包,我们需要每次创建直线函数的时候同时说明a,b,x。这样,我们就需要更多的参数传递,也减少了代码的可移植性

    5. 注意点

    5.1 使用闭包应注意的问题

    由于闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存。因此可以手动解除对匿名函数的引用,以便释放内存。

    5.2 修改外部函数中的变量

    def counter(start=0):
        def add_one():
            nonlocal start
            start += 1
            return start
        return add_one
    
    c1 = counter(5)  # 创建一个闭包
    print(c1())
    print(c1())
    
    c2 = counter(50)  # 创建另外一个闭包
    print(c2())
    print(c2())
    
    print(c1())
    print(c1())
    
    print(c2())
    print(c2())
    

      

     

    运行效果:

    image-20190301074352590

    5.3 多个闭包

    如上面的代码中,调用了2次counter,也就意味着创建了2个闭包,并且每个闭包之间没有任何关系。

    大家是否有种感觉,好像闭包与对象有些类似。确实是这样的,对象其实可通俗的理解为数据(属性)+功能(方法),而闭包也可以理解为数据+功能,只不过此时数据是外部函数中的那些局部变量或者形参,而功能则是内部函数。对象适合完成较为复杂的功能,而闭包则更轻量

    6. 充分理解闭包

    问题:初中里学过函数,例如y=kx+b

    y=kx+b为例,请计算一条线上的多个点 即 给x值 计算出y

    6.1 方式1

    # 第1种
    k = 1
    b = 2
    y = k*x+b
    

      

     

    缺点:如果需要多次计算,那么就的写多次y = k*x+b这样的式子

    6.2 方式2

    # 第2种
    def line_2(k, b, x):
        print(k*x+b)
    
    line_2(1, 2, 0)
    line_2(1, 2, 1)
    line_2(1, 2, 2)
    

      

     

    缺点:如果想要计算多次这条线上的y值,那么每次都需要传递kb的值,麻烦

    6.3 方式3

    # 第3种: 全局变量
    k = 1
    b = 2
    def line_3(x):
        print(k*x+b)
    
    line_3(0)
    line_3(1)
    line_3(2)
    k = 11
    b = 22
    line_3(0)
    line_3(1)
    line_3(2)
    

      

     

    缺点:如果要计算多条线上的y值,那么需要每次对全局变量进行修改,代码会增多,麻烦

    6.4 方式4

    # 第4种:缺省参数
    def line_4(x, k=1, b=2):
        print(k*x+b)
    
    line_4(0)
    line_4(1)
    line_4(2)
    
    line_4(0, k=11, b=22)
    line_4(1, k=11, b=22)
    line_4(2, k=11, b=22)
    

      

     

    优点:比全局变量的方式好在:k, b是函数line_4的一部分 而不是全局变量,因为全局变量可以任意的被其他函数所修改

    缺点:如果要计算多条线上的y值,那么需要在调用的时候进行传递参数,麻烦

    6.5 方式5

    # 第5种:实例对象
    class Line5(object):
        def __init__(self, k, b):
            self.k = k
            self.b = b
    
        def __call__(self, x):
            print(self.k * x + self.b)
    
    
    line_5_1 = Line5(1, 2)
    # 对象.方法()
    # 对象()
    line_5_1(0)
    line_5_1(1)
    line_5_1(2)
    line_5_2 = Line5(11, 22)
    line_5_2(0)
    line_5_2(1)
    line_5_2(2)
    

      

     

    缺点:为了计算多条线上的y值,所以需要保存多个k, b的值,因此用了很多个实例对象, 浪费资源

    6.6 方式6

    # 第6种:闭包
    
    def line_6(k, b):
        def create_y(x):
            print(k*x+b)
        return create_y
    
    
    line_6_1 = line_6(1, 2)
    line_6_1(0)
    line_6_1(1)
    line_6_1(2)
    line_6_2 = line_6(11, 22)
    line_6_2(0)
    line_6_2(1)
    line_6_2(2)
    

      

     

    6.7思考

    函数、匿名函数、闭包、对象 当做实参时 有什么区别?

    1. 匿名函数能够完成基本的简单功能,,,传递是这个函数的引用 只有功能

    2. 普通函数能够完成较为复杂的功能,,,传递是这个函数的引用 只有功能

    3. 闭包能够将较为复杂的功能,,,传递是这个闭包中的函数以及数据,因此传递是功能+数据

    4. 对象能够完成最为复杂的功能,,,传递是很多数据+很多功能,因此传递是功能+数据

    7. 闭包应用

    7.1 应用1

    下面应用案例是理解闭包的经典题目,模拟了一个人站在原点,然后向X、Y轴进行移动,每次移动后及时打印当前的位置

    def create():
        pos = [0, 0]  # 坐标系统原点
        def player(direction, step):
            # 这里应该首先判断参数direction,step的合法性,比如direction不能斜着走,step不能为负等
            # 然后还要对新生成的x,y坐标的合法性进行判断处理,这里主要是想介绍闭包,就不详细写了
            new_x = pos[0] + direction[0] * step
            new_y = pos[1] + direction[1] * step
            pos[0] = new_x
            pos[1] = new_y
            return pos
    
        return player
    
    
    player = create()  # 创建棋子player,起点为原点
    print(player([1, 0], 10))  # 向x轴正方向移动10步
    print(player([0, 1], 20))  # 向y轴正方向移动20步
    print(player([-1, 0], 10))  # 向x轴负方向移动10步
    

      

     

    运行效果:

    image-20190228213656114

    7.2 应用2

    有时我们需要对某些文件的特殊行进行分析,先要提取出这些特殊行

    例如,需要取得文件"result.txt"中含有"163.com"关键字的行,看如下代码

    def make_filter(keep):  
        def the_filter(file_name):  
            file = open(file_name)  
            lines = file.readlines()  
            file.close()  
            filter_doc = [i for i in lines if keep in i]  
            return filter_doc  
        return the_filter  
    
    filter = make_filter("163.com")  
    filter_result = filter("result.txt")
    

      

     

    运行效果:

    image-20190228214830782

    8. 总结

    1. 闭包定义是在函数内再嵌套函数
    2. 闭包是可以访问另一个函数局部作用域中变量的函数
    3. 闭包可以读取另外一个函数内部的变量
    4. 闭包可以让参数和变量不会被垃圾回收机制回收,始终保持在内存中(而普通的函数调用结束后 会被Python解释器自动释放局部变量)
  • 相关阅读:
    gobject对象不宜作为动态加载的插件
    用内存管理器的钩子函数跟踪内存泄漏
    DBUS与多线程
    broncho小组放假半天为中国奥运加油
    多进程DirectFB用X11显示的死锁问题
    佛诞节快乐
    R语言中substr函数,字符串截取函数
    R语言中while循环
    R语言中求分位数
    R语言中利用sapply函数提取列表中元素
  • 原文地址:https://www.cnblogs.com/dong4716138/p/15774369.html
Copyright © 2020-2023  润新知