美文网首页
闭包﹝Python﹞

闭包﹝Python﹞

作者: import_hello | 来源:发表于2018-12-30 11:31 被阅读0次

    Closures

    转载须注明出处:简书@Orca_J35 | GitHub@orca-j35,所有笔记均托管于 python_notes 仓库

    内函数最重要的一个特性是会自动执行闭包操作。我们都知道内层函数可以引用外层函数的局部变量(或参数),这些被内层函数引用的非局部变量会被绑定到内层函数的 __closure__ 属性中。将非局部变量绑定到内层函数中的过程被称为"闭包",经历过闭包操作的函数被称为"闭包函数",被绑定的非局部变量被称为闭包函数的"自由变量"。

    def sum_():  # sum_ 是外函数
        x = 1
        y = 2
    
        def inner():  # inner 是内函数
            return x + y # x和y是自由变量
        # 内函数经历过闭包,其__closure__属性是由cell组成的元组
        print("__closure__:", inner.__closure__)
        print("cell_contents:", inner.__closure__[0].cell_contents)
    
    sum_()
    print(sum_.__closure__)
    '''Out:
    __closure__: (<cell at 0x0000017D9898B618: int object at 0x000000006D506C20>, <cell at 0x0000017D9898B978: int object at 0x000000006D506C40>)
    cell_contents: 1
    None
    '''
    

    函数对象的 __closure__ 属性与闭包操作相关,如果某个函数经历过闭包的话(比如,inner),那么该属性的值是一个由 cell 对象组成的元组;否则是 None (比如,sum_)。cell 对象的 cell_contents 属性用于绑定自由变量的值。

    函数对象作返回值

    函数在 Python 中是第一类对象,可作为另一个函数的返回值(注意,被返回的是函数对象,并且函数对象在返回过程中不会被执行)。现在我们来考虑如下例子:

    def generate_power(power):
        def nth_power(number):
            return number ** power
        return nth_power
    '''
    >>> two_power = generate_power(2) # 仅返回函数对象
    >>> two_power(5) # 调用被返回的函数对象
    25
    >>> three_power = generate_power(3)
    >>> three_power(4)
    64
    '''
    

    内函数被返回后,虽然外函数的生命周期已结束,但由于内函数经历过闭包,所以内函数中已绑定了自由变量。因此,从外部调用内函数时,power 参数依旧可用。

    另外,内函数是在外函数被调用后才被创建的,所以每次返回的内函数均有不同的 id。即便使用相同的参数调用外层函数,也是如此。

    >>> two_power = generate_power(2)
    >>> two_power_ = generate_power(2)
    >>> two_power is two_power_
    False # 即便参数相同,也会返回两个不同id的函数
    

    当我们在外部调用内函数时,外层函数已执行完毕,自由变量均处于外函数执行完毕时的状态。也就是说,如果我们在创建内函数后,又修改过自由变量的话,当我们在外部调用内函数时,会使用已被修改过的自由变量。考虑下面的示例:

    def count():
        funcs = []
        for i in [1, 2, 3]: # 每次循环均会修改自由变量i
            # 每次循环都会创建一个内函数,并将其保存到funcs中
            def f(): 
                return i
            funcs.append(f)
        return funcs
    # count执行完毕时,变量i的值是3,所以三个函数对象对象中的i都等于3
    """
    >>> f1, f2, f3 = count()
    >>> f1(), f2(), f3()
    (3, 3, 3)
    """
    

    i 是内函数 f 的自由变量,在外函数退出时等于 3。当我们调用 funcs 列表中存放的内函数时,由于自由变量 i 的值是 3,所以内函数的返回值也都是 3。因此,我们应避免在闭包函数中引用将来会发生变化的自由变量。如果非要使用这样的变量,可采用如下方案:再创建一层函数,通过调用该层函数来锁定最内层函数的自由变量。

    def count():
        funcs = []
        for i in [1, 2, 3]:
            def g(param):
                f = lambda : param    # 这里创建了一个匿名函数
                return f
            funcs.append(g(i))        # 将循环变量的值传给 g
        return funcs
    """
    >>> f1, f2, f3 = count()
    >>> f1(), f2(), f3()
    (1, 2, 3)
    """
    

    模拟类和实例

    "闭包操作"可将函数和数据环境(自由变量)进行绑定,这一点与类非常相似。我们可以利用闭包操作来模拟类和实例。一般来说,当类中只有一个方法时,换用闭包函数是更好的选择。

    下面这个类,用于求解定点间的距离:

    from math import sqrt
    
    
    class Point(object):
        def __init__(self, x, y):
            self.x, self.y = x, y
    
        def get_distance(self, u, v):
            distance = sqrt((self.x - u) ** 2 + (self.y - v) ** 2)
            return distance
    
    
    pt = Point(7, 2)
    print(pt.get_distance(10, 6)) # Out: 5.0
    

    用闭包操作实现上面的类:

    def point(x, y):
        def get_distance(u, v):
            return sqrt((x - u) ** 2 + (y - v) ** 2)
    
        return get_distance
    
    
    pt = point(7, 2)
    print(pt(10, 6)) # Out: 5.0
    

    可见,闭包比类更加简洁。

    参考

    相关文章

      网友评论

          本文标题:闭包﹝Python﹞

          本文链接:https://www.haomeiwen.com/subject/foxslqtx.html