中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Python 中閉包概念是什么

發布時間:2021-07-06 12:01:40 來源:億速云 閱讀:121 作者:chen 欄目:編程語言

本篇內容介紹了“Python 中閉包概念是什么”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!


 

概要

計算機中有些英文專業詞匯,字面直譯,難免因缺少上下文而顯得蒼白拗口,須得多方鋪墊,方能味得古怪下面的原理。閉包(closure)便是一個這樣牽扯了許多上下文的概念,包括編程語言最基本的綁定(binding),環境(environments),變量作用域(scope)以及函數是第一等公民(function as the first-class)等等。

 

Binding(綁定)

在Python中,binding(綁定) 是編程語言最基本的抽象手法,它將一個值綁定到一個變量上,并且稍后可以引用或者修改該變量。下面是幾種不同層次的綁定,每組語句在運行時將一個名字與對應值綁定到其定義所在的環境中。

  • 將名字綁定到一塊內存,通過賦值語句實現,當然函數調用時,形參和實參結合也是綁定:
In [1]: square = 4
 
  • 將名字綁定到一組復合運算,即     函數定義,利用     def 關鍵字實現:
In [1]: def square(x):
            return x*x
 
  • 將名字綁定到一個數據集合,即     類定義,使用     class 實現:
In [1]: class square:
            def __init__(self, x):
                self.x = x

            def value(self):
                return self.x * self.x
 

依照執行順序,同名多次綁定,后面會覆蓋前面

In [1]: square = 3

In [2]: square
Out[2]: 3

In [3]: def square(x):
   ...:     return x * x
   ...:
   ...:

In [4]: square
Out[4]: <function __main__.square(x)>

In [5]: class square:
   ...:     def __init__(self, x):
   ...:         self.x = x
   ...:

In [6]: square
Out[6]: __main__.square
 

說這些都是抽象,是因為它們提供了對數據、復合操作或數據集合的封裝手段,即將一個名稱與復雜的數據或邏輯進行捆綁,使調用者不用關心其實現細節,并可以據此來構建更復雜的工程。可以說綁定是編程的基石。

回到本文的主題上來,閉包是對一組復合語句的抽象,也就是函數,只不過是一種特殊的函數,至于這個特殊性在哪,這里先賣個關子,等稍后引入更多概念后再進行闡述。

 

Scope (作用域)

scope(作用域),顧名思義,也就是某個binding 能罩多大的范圍,或者說可以在多大范圍內訪問的到一個變量。每個函數定義會生成一個局部定義域。

Python,和大多數編程語言一樣,使用的是靜態作用域(static scoping,有時也稱 lexical scoping)規則。在函數嵌套定義的時候,內層函數內可以訪問外層函數的變量值。因此你可以把作用域想象成一個容器,即它是可以嵌套的,并且內層作用域會擴展外層作用域,而最外層作用域即全局作用域。

上一小節提到了,多次同名綁定,后面會覆蓋先前,其實有隱含前提:在同一作用域內。如果是嵌套作用域,其實是隱藏的關系,內層函數的變量定義會遮蔽外層函數同一名字定義,但是在外層作用域中,該變量仍是原值:

In [16]: a = 4

In [17]: def outer(): 
    ...:     a = 5
    ...:     print(a)
    ...:     def inner():
    ...:         a = 6
    ...:         print(a)
    ...:     inner()
    ...:     print(a)
    ...:

In [18]: outer()
5
6
5

In [19]: print(a)
4
 

作用域其實也可以從另一個角度理解,即我們在某個環境(environment)中,在確定一個name binding 值的時候,會從最內層作用域順著往外找,找到的第一個該名字 binding 的對應的值即為該 name 引用到的值。

需要強調的時候,函數的嵌套定義會引起定義域的嵌套,或者說環境擴展(內層擴展外層)關系。類的定義又稍有不同,class 定義會引入新的 namespace(命名空間),命名空間和作用域是常拿來對比的概念,但這里按下不表,感興趣的可以自己去查查資料。

說到這里,要提一下,一個常被說起的反直覺例子:

In [50]: a = 4

In [51]: def test():
    ...:     print(a) # 這里應該輸出什么?
    ...:     a = 5
    ...:

In [52]: test()
---------------------------------------------------------------------------
UnboundLocalError                         
Traceback (most recent call last)
<ipython-input-52-fbd55f77ab7c> in <module>()
----> 1 test()

<ipython-input-51-200f78e91a1b> in test()
      1 def test():
----> 2     print(a)
      3     a = 5
      4

UnboundLocalError: local variable 'a' referenced before assignment
 

想象中,上面 print 處應該輸出 4 或者 5 才對,為什么會報錯呢?這是因為 test 函數在被解釋器解析的時候,分詞器會掃一遍 test 函數定義中的所有 token(符號),看到賦值語句 a=5 的存在,就會明確 a 是一個局部變量,因此不會輸出 4。而在執行到 print(a) 的時候,在局部環境中,a 還未被binding,因此會報 UnboundLocalError

稍微擴展說明一下,雖然 Python 是解釋執行的,即輸入一句,解釋一句,執行一句。但是對于代碼塊(即頭部語句,冒號與其關聯的縮進塊所構成的復合語句(compound sentences),常見的有函數定義,類定義,循環語句等等)來說,還是會整體先掃一遍的。

 

First-Class Function(函數是第一等公民)

一般來說,組成編程語言的元素,如變量、函數和類,會被設定不同的限制,而具有最少限制的元素,被我們稱為該編程語言中的一等公民。而一等公民最常見的特權有:

  1. 可以被     綁定到名字上
  2. 可以作為參數在函數中傳遞
  3. 可以作為返回值被函數作為結果返回
  4. 可以被包含在其他數據結構中

套用到 Python 中的函數,即一個函數可以被賦值給某個變量,可以被其他函數接收和返回,可以定義在其他函數中(即嵌套定義):

In [32]: def test():
    ...:     print('hello world')
    ...:

In [33]: t = test # 賦值給變量

In [34]: t()
hello world

In [35]: def wrapper(func):
    ...:     print('wrapper')
    ...:     func()
    ...:

In [36]: wrapper(t) # 作為參數傳遞
wrapper
hello world

In [37]: def add_num(a): 
    ...:     def add(b): # 嵌套定義
    ...:         return a + b
    ...:     return add # 作為函數的返回值
    ...:
    ...:

In [38]: add5 = add_num(5)

In [39]: add5(4)
Out[39]: 9
 

并不是在所有語言中,函數都是一等公民,比如 Java8 以前的 Java,上面四項權利 Java7 中的函數后幾項都沒有。使用函數作為第一等公民的做法,我們成為函數式編程。在這個大數據時代,由于對并發的友好性,傳統過程式語言(比如 Cpp、Java)都在新版本上逐漸支持函數式編程范式。

在這里,能夠操作其他函數的函數(即以其他函數作為參數或者返回值的函數),叫做高階函數。高階函數使得語言的表達能力大大增強,但同時,也增加了編程復雜度。

 

Stack Call(棧式調用)

每個函數調用,會在環境中產生一個 frame棧幀),并且在棧幀中會進行一些綁定,然后壓入函數調用棧中。在函數調用結束時,棧幀會被彈出,其中所進行的綁定也被解除,即垃圾回收,對應的局部作用域也隨之消亡。

In [47]: def test():
    ...:     x = 4
    ...:     print(x)
    ...:

In [48]: test()
4

In [49]: x
---------------------------------------------------------------------------
NameError                                 
Traceback (most recent call last)
<ipython-input-49-6fcf9dfbd479> in <module>()
----> 1 x

NameError: name 'x' is not defined
 

即在調用結束后,局部定義的變量  x 在外邊是訪問不到的。但是如之前例子中,返回的 add 函數卻引用了已經調用結束的 add_num 中的變量 a,怎么解釋這種現象呢?可以記住一條,也是之前提到過的:

函數嵌套定義時,內部定義的函數所在的環境會自動擴展其定義所在環境
 

因此在外部函數返回后,返回的內部函數依然維持了其定義時的擴展環境,也可以理解為由于內部函數引用的存在,外部函數的環境中所有的綁定并沒有被回收。

 

Closure(閉包)

千呼萬喚始出來,以為是高潮,其實已結束。

閉包就是建立在前面的這些概念上的,上面提到的某個例子:

In [37]: def add_num(a): 
    ...:     def add(b): # 嵌套定義
    ...:         return a + b
    ...:     return add # 作為函數的返回值
    ...:
    ...:

In [38]: add5 = add_num(5)

In [39]: add5(4)
Out[39]: 9
 

其實就是閉包。撿起之前伏筆,給出我對閉包的一個理解:它是一種高階函數,并且外層函數(例子中的add_num)將其內部定義的函數(add)作為返回值返回,同時由于返回的內層函數擴展了外層函數的環境,也就是對其產生了一個引用,那么在調用返回的內部函數(add5)的時候,能夠引用到其(add)定義時的外部環境(在例子中,即 a 的值)。

結語

說了這么多,其實只是在邏輯層面或者說抽象層面去解釋閉包是什么,常跟哪些概念糾纏在一起。但這些都沒有真正觸到其本質,或者說依然是空中樓閣,如果想要真正理解閉包,可以去詳細了解下 Python 的解釋執行機制,當然,那就是編譯原理的范疇了。

“Python 中閉包概念是什么”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

清水县| 栖霞市| 乐业县| 丹巴县| 泽库县| 鄂托克前旗| 泉州市| 且末县| 建始县| 宁河县| 乐安县| 桃园市| 浑源县| 潍坊市| 兴山县| 巩留县| 荔浦县| 龙井市| 镶黄旗| 虎林市| 尤溪县| 勐海县| 布拖县| 绥德县| 弋阳县| 会宁县| 达州市| 平凉市| 安阳县| 泸西县| 陵水| 广河县| 大庆市| 宾阳县| 芦溪县| 承德市| 罗山县| 尖扎县| 新竹县| 大邑县| 永昌县|