• python-闭包和装饰器-01-闭包(closure)


    闭包(closure)

    闭包就是在一个函数定义的内部再定义一个函数,并且这个函数用到了外边函数的变量,那么将这个函数以及用到的一些变量称之为闭包,如:

    def line(a, b):
        def cal(c):
            return a + b + c
        return cal

    定义了一个line函数,在line内部又定义了一个函数cal,内部函数cal中使用到了外部函数line的变量(a,b)并且line函数返回cal函数,调用例子如下:

    print('------line1---------')
    line1 = line(1, 2)
    print('line1指向:', line1)
    print(line1(3))
    print(line1(4))
    print('--------line2---------')
    line2 = line(2, 3)
    print('line2指向:', line2)
    print(line2(10))
    print(line2(20))

    运行结果为:

    1、可以看出每次调用的line函数,都会返回一个cal方法,且每个cal方法都有一个独立的内存空间,即line1和line2指向的空间地址不一样

    2、可以多次调用line1或者line2,且多次调用同一个line1或者line2时,共用了上面的a和b变量

    3、line1和line2调用时相互独立,不会互相影响

    为什么使用闭包

    为什么要使用闭包,它和普通的函数和类又有什么区别,这里举一个例子来说明,将上面的line例子替换成一个一元一次方程方程来理解:y=kx+b,这个方程中k和b值的不同,对应在坐标中的就是不同斜率和不同于y轴相交的线,简单理解就是不同形状的直线。那么现在的需求是通过x的值不同,计算出y的值,比如,当k=1,b=1时,y = x+1,定义成函数即为

    def line(x):
        return x + 1
    print(line(1))
    print(line(2))
    print(line(3))
    # 结果为
    2
    3
    4

    而上面函数中,如果我想随便换一个k和b的值,就可以改进为

    def line(k, x, b):
        return k * x + b
    print(line(1, 2, 1))
    print(line(1, 2, 2))
    print(line(1, 2, 3))
    # 结果为
    3
    4
    5

    上面两个函数,都各有优缺点:

    对于第一个函数:

      优点是:对于同一种形状的线(k和b不变),每次只需要传入x的值就可以了

      缺点是:如果想用其他形状的线,即随意设置k和b,每次都要改函数中的代码

    对于第二个函数:

      优点是:可以随意设置k、b、x的值

      缺点是:如果想多次求同一形状的线(k和b不变),那么需要多次重复写相同的k和b参数,如 line(1, 2, 1)、line(1, 2, 2)、line(1, 2, 3),重复写了参数k=1和b=2

    可以发现其实他们的优缺点其实是互补的,那么能不能有一种方式可以同时具有上面两种函数的优点呢?

    即既可以随意设置k和b值,且对于同一种k和b的搭配,只需设置一次k和b的值就可以保存起来,然后每次调用时只需要传入x即可。这种需求可以通过类来实现,如:

    class Line(object):
        def __init__(self, k, b):
            self.k = k
            self.b = b
    
        def get_y(self, x):
            return self.k * x + self.b
    
    print('--------line1--------')
    line1 = Line(1, 2)
    print(line1.get_y(1))
    print(line1.get_y(2))
    print(line1.get_y(3))
    print('--------line2--------')
    line2 = Line(10, 20)
    print(line2.get_y(1))
    print(line2.get_y(2))
    print(line2.get_y(3))
    
    # 结果为:
    --------line1--------
    3
    4
    5
    --------line2--------
    30
    40
    50

    这样我们需要不同形状(k和b)的线的时候,就可以创建一次不同的line对象(即不需要重复设置k和b),使用line对象获取y值时(调用get_y),也只需要传入x即可。

    通过类确实可以实现需求,但是其实为了一个简单的方法而去创建一个类,有点杀鸡用宰牛刀的意思,因为每次创建Line对象都会调用很多用不到的魔法方法以及继承父类的一些东西等,这样会占用一些不必要的空间或者资源,性价比太低了,而创建函数所需占用的空间比创建类要小得多,那么我们这里就可以使用闭包来实现了。如:

    def line(k, b):
        def get_y(x):
            return k * x + b
        return get_y
    
    print('--------line1--------')
    line1 = line(1, 2)
    print(line1(1))
    print(line1(2))
    print(line1(3))
    print('--------line2--------')
    line2 = line(10, 20)
    print(line2(1))
    print(line2(2))
    print(line2(3))
    
    # 结果为:
    --------line1--------
    3
    4
    5
    --------line2--------
    30
    40
    50

    可以理解为,当调用line(1, 2)时,就会创建一块内存空间,用来存储传入的参数 k=1,b=2 以及 get_y的定义代码,然后将变量名line1指向该空间地址,当调用line1(1)时,就会调用里面的get_y方法,并使用到之前传入的k和b的值。当调用新的line(10, 20)时,又会创建一块新的内存空间存储k=10,b=20 以及 get_y的定义代码。所以每次调用闭包的外层方法时,就像是面向对象中创建了一个类的实例,并开辟了一块独立的内存空间,里面存着实例属性(外层函数的参数,如k和b)和实例方法(内层函数)。

    通过nonlocal修改外部函数的局部变量

    闭包中,内部函数能够直接使用外部函数的变量和参数,但是不能直接修改,如下面例子:

    def line(a):
        num = 0
        print('初始,num:%d, id:%d' % (num, id(num)))
        print('初始,a:%d, id:%d' % (a, id(a)))
    
        def get_y():
            # nonlocal num, a
            num = 1
            a = 200
            print()
            print('get_y内,num:%d, id:%d' % (num, id(num)))
            print('get_y内,a:%d, id:%d' % (a, id(a)))
        get_y()
        print()
        print('外,num:%d, id:%d' % (num, id(num)))
        print('外,a:%d, id:%d' % (a, id(a)))
        return get_y
    
    line1 = line(100)

    运行结果为:

    初始,num:0, id:140724620236416
    初始,a:100, id:140724620239616
    
    get_y内,num:1, id:140724620236448
    get_y内,a:200, id:140724620242816
    
    外,num:0, id:140724620236416
    外,a:100, id:140724620239616

    1、初始局部变量指向的是....6416和....9616对应的地址,该地址上存着0和100两个值

    2、在内部函数get_y中企图修改上面的局部变量对应的值,即创建了两个新地址....6448和....2816,存着1和200,想让上面的局部变量不再指向原来的地址,而是指向新创建的地址

    3、但结果发现原局部变量还是指向的原地址,即变量对应的值不变,说明并没有改变原来的值

    想要修改外部局部变量时,需要在修改前通过nonlocal声明,如:

    def line(a):
        num = 0
        print('初始,num:%d, id:%d' % (num, id(num)))
        print('初始,a:%d, id:%d' % (a, id(a)))
    
        def get_y():
            nonlocal num, a
            num = 1
            a = 200
            print()
            print('get_y内,num:%d, id:%d' % (num, id(num)))
            print('get_y内,a:%d, id:%d' % (a, id(a)))
        get_y()
        print()
        print('外,num:%d, id:%d' % (num, id(num)))
        print('外,a:%d, id:%d' % (a, id(a)))
        return get_y
    
    line1 = line(100)

    运行结果为:

    初始,num:0, id:140724620236416
    初始,a:100, id:140724620239616
    
    get_y内,num:1, id:140724620236448
    get_y内,a:200, id:140724620242816
    
    外,num:1, id:140724620236448
    外,a:200, id:140724620242816

    发现最后外部的局部变量成功被修改了,即外部的局部变量num和a的指向变成了新的地址....6448和...2816

     

  • 相关阅读:
    LeetCode 172:阶乘后的零
    Ubuntu12.04更新出现 The system is running in low-graphics mode解决方法
    不加参数的存储过程
    PCC-S-02201, Encountered the symbol "DB_USER_OPER_COUNT"
    该思考
    关于export环境变量生存期
    会话临时表 ORA-14452
    如何创建守护进程--及相关概念
    2014年10月末
    6个月
  • 原文地址:https://www.cnblogs.com/gcxblogs/p/12974493.html
Copyright © 2020-2023  润新知