面試題-python 什么是閉包(closure)?


前言

前面學了裝飾器,那么閉包和裝飾器有什么區別呢?
閉包傳遞的是變量,而裝飾器傳遞的是函數對象,只是傳的參數內容不一樣,閉包的概念包含了裝飾器,可以說裝飾器是閉包的一種,它只是傳遞函數對象的閉包。

先看一個面試題

先看一個經典的面試題,很有代表性, 運行以下代碼會輸出什么呢?為什么會是這種結果?

def fun():
    temp = [lambda x: i*x for i in range(4)]
    return temp

for everyLambda in fun():
    print(everyLambda(2))

運行結果

6
6
6
6

運行的結果是4個6 ,並不是我們想的 :0, 2, 4, 6。上面的代碼用到了列表推導式,還有個匿名函數lambda,直接去閱讀不太好理解,可以把匿名函數轉成自己定義一個函數.
於是上面的代碼等價於:

"""
# 作者-上海悠悠 QQ交流群:717225969
# blog地址 https://www.cnblogs.com/yoyoketang/
def fun():
    temp = [lambda x: i*x for i in range(4)]
    return temp

for everyLambda in fun():
    print(everyLambda(2))
"""


def fun():
    temp = []
    for i in range(4):
        def inner(x):
            return i*x
        temp.append(inner)
    return temp

for everyLambda in fun():
    print(everyLambda(2))

為了更好的理解,可以先去掉外面的一層fun()

temp = []
for i in range(4):
    def inner(x):
        return i*x
    temp.append(inner)

for everyLambda in temp:
    print(everyLambda(2))

這里只定義了一個函數 inner(), 有 2 個變量,i 是函數外部的變量,x 是函數內部的變量。
現在問題的關鍵在理解函數外部變量和函數內部變量的區別了, 接下來再看一個簡單的例子

# 作者-上海悠悠 QQ交流群:717225969
# blog地址 https://www.cnblogs.com/yoyoketang/


a = 1


def myfunc(b):
    return a+b

print(myfunc(100))
a = 2
a = 3
print(myfunc(100))
print(myfunc(100))

運行結果:101 103 103
也就是函數外部變量a是可變的,后面給a重新賦值了,會替換前面的值。上面的 inner(x) 函數也是一樣,外部變量 i 的值是0, 1, 2, 3變化,最后的3 會覆蓋前面的值,所以得到的結果都是6
如何解決上面的問題,接下來就是要說的閉包的概念了!

什么是閉包?

閉包就是外部函數中定義了一個內部函數,當外部函數返回內部函數對象(注意是函數對象)時,程序接收了內部函數的定義(此時並未被執行),當再次執行這個返回值時,這個被返回的函數才能被執行。
創建一個閉包必須滿足以下幾點:

  • 必須有一個內嵌函數
  • 內嵌函數必須引用外部函數中的變量
  • 外部函數的返回值必須是內嵌函數

閉包和裝飾器的區別:閉包傳遞的是變量,而裝飾器傳遞的是函數,除此之外沒有任何區別,或者說裝飾器是閉包的一種,它只是傳遞函數的閉包。
以下是閉包的一個標准示例:

# 作者-上海悠悠 QQ交流群:717225969
# blog地址 https://www.cnblogs.com/yoyoketang/


def outer(age):
    def inner(name):
        print("my name is %s. my age is %s." % (name, age))
    return inner

demo = outer("18")
demo("yoyo")
# 運行結果:my name is yoyo. my age is 18.

上面的問題,用閉包來解決

# 作者-上海悠悠 QQ交流群:717225969
# blog地址 https://www.cnblogs.com/yoyoketang/


# 閉包
def temp(i):      # 一個外層函數
    def inner(x):    # 內層函數
        return i*x
    return inner

a = []
for i in range(4):
    a.append(temp(i))
print(a)

for j in a:
    print(j(2))

運行結果

[<function temp.<locals>.inner at 0x000002A8EE929AE8>, 
<function temp.<locals>.inner at 0x000002A8EE929B70>, 
<function temp.<locals>.inner at 0x000002A8EE929BF8>, 
<function temp.<locals>.inner at 0x000002A8EE929C80>]
0
2
4
6

使用列表推導式

# 閉包
def temp(i):      # 一個外層函數
    def inner(x):    # 內層函數
        return i*x
    return inner


def fun():
    temps = [temp(i) for i in range(4)]
    return temps

for everyLambda in fun():
    print(everyLambda(2))

這樣就可以得到我們的預期結果:0 2 4 6

通過上面的案例就可以了解到閉包的作用了,它保存了函數的外部變量,不會隨着變量的改變而改變了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM