解讀Python函數閉包的概念及作用域

2021-01-10 米粒教育

在前面的內容中,我們討論了全局變量和局部變量的作用域,也討論了嵌套函數的作用域,並了解了局部變量或嵌套函數僅限於在函數體內使用。但在一些情況下,可以將函數內部的嵌套函數引入到全局環境中使用,Python將引入到全局環境中使用的嵌套函數及其環境變量構建成一個封閉的包,該包內的環境變量不受外部環境的影響,這就是我們將要討論的閉包。

前面我們了解了嵌套函數的作用域僅限於其父函數體內,如果在父函數體外調用其嵌套的函數,就會超出嵌套函數的作用域。

上面的代碼定義了line_conf函數,在line_conf函數體內有嵌套定義了計算直線方程的函數line。依據前面學過的知識,我們會推斷出在line_conf函數體內調用line函數是合法的,但在line_conf函數體外調用line函數是非法的,因為在line_conf函數體外調用line函數已經超越了line函數的作用域。上面代碼的執行結果也驗證了我們的推斷。

在Python語言中,當父函數體內定義了嵌套函數後,父函數可以把定義的嵌套函數作為嵌套函數的引用返回給調用者。

函數的引用是什麼呢?當我們定義一個函數時,不管是父函數還是父函數體內的嵌套函數。Python解釋器都會為定義的函數分配內存空間,用於存儲函數的代碼、使用的變量等等,該內存的地址被賦值給函數名稱所標識的存儲單元,函數調用者可以通過函數名稱所標識的存儲單元找到函數的內存地址,並執行該函數。

由此看來,函數名稱也是一個變量,它存儲了函數的內存地址。函數的內存地址既能賦值給函數名稱,也可以通過函數名稱賦值給其它變量,只不過其它變量存儲的不是函數的直接內存地址,而是函數名稱的內存地址。例如前面代碼定義的line函數,可以把line函數名稱的地址賦值給變量a和b,執行a、b、line的效果都是一樣的。

上面的代碼把line函數名稱分別賦值給局部變量a和b,分別執行line(5)、a(5)、b(5),其執行結果是相同的。由此可以證明,變量a和b與line指向同一個函數。在這種情況下,我們說a和b是line函數的引用。

理解了什麼是函數的引用後,我們就很容易理解把函數作為一個引用返回給調用者了。當父函數把父函數體內的嵌套函數名稱返回給調用者時,實際上是把嵌套函數名稱的內存地址返回給調用者,調用者將返回的函數名稱內存地址賦值給接收變量,調用者通過接收的變量就可以執行接收變量所引用的函數。

上面的代碼把line_conf函數內部定義的line嵌套函數返回給調用者,調用者將line嵌套函數的內存地址賦值給my_line變量,最後執行my_line所引用的line函數。執行結果如下圖所示。

現在我們已經實現了把父函數中的嵌套函數引入到全局環境中,嵌套函數可以在父函數體外被調用。這已經是閉包的概念了,嵌套函數作為一個獨立的執行環境被外部引用,此時被外部環境獨立引用的嵌套函數已經脫離了父函數體,構成一個獨立的運行環境。

我們都知道,局部變量在函數執行完成後就被銷毀了。那麼,如果在line函數中使用了line_conf的變量,當line_conf函數執行完成後,返回到全局環境的line函數還能使用line_conf的中的變量嗎?

在上面的代碼中,line函數使用了其父函數聲明的變量b,變量b在line函數的定義之外,此時b為line的環境變量。當line函數作為line_conf函數的返回值時,變量b的取值已經和line函數綁定在一起,也就是說父函數和line函數綁定的變量b的值已經沒有關係了,變量b即使再有變化,也不會影響到line函數的計算結果。在這種情況下,我們說line函數和它的環境變量b構成了一個閉包,閉包是一個獨立的運行環境,不受外部環境的影響和約束。上面的代碼輸出結果為25,而不是15。

下面是簡單使用閉包的例子,模擬一個計數器。

上面的代碼定義了counter函數,它所做的事情就是接收一個初始值並開始計數,初始值賦值給列表count的一個成員。然後在內部定義一個嵌套函數incr,incr使用了父函數的局部變量count,count也是incr函數的環境變量,這樣就創建了一個閉包,因為incr函數包含了counter函數中局部變量的作用域。下圖是上面代碼執行後的輸出結果。

閉包可以將函數和它需要的數據關聯起來而不受外部影響。從這點來看,閉包和面向對象基本類似,面向對象也是將數據和行為封裝成一個類。在一些情況下使用類也許是最好的方式,閉包更適合函數式編程,函數式編程我們將放在後面討論。

相關焦點

  • 第55p,閉包函數,函數知識的綜合運用
    大家好,我是楊數Tos,這是《從零基礎到大神》系列課程的第55篇文章,第三階段的課程:Python進階知識:Python進階知識:詳細講解Python中的函數(八)====> 函數的嵌套調用之閉包函數。
  • go 學習筆記之僅僅需要一個示例就能講清楚什麼閉包
    如果能夠直接理解英文的同學可以略過這部分的中文翻譯,要是不願意費腦理解英文的小夥伴跟我一起解讀中文吧!自由變量這裡使用了一個比較陌生的概念: 自由變量(在本地使用但在封閉範圍內定義的變量)很顯然,根據括號裡面的注釋說明我們知道: 所謂的自由變量是相對於閉包函數或者說匿名函數來說的外部變量,由於該變量的定義不受自己控制,所以對閉包函數自己來說就是自由的,並不受閉包函數的約束!那麼按照這種邏輯繼續延伸猜測的話,匿名函數內部定義的變量豈不是約束變量?
  • Python基礎教程(一) - 函數和函數式編程
    前面使用過很多print()來進行列印,這是python提供的內建函數,你也可以自己創建函數,這叫做用戶自定義函數。創建函數你可以定義一個由自己想要功能的函數,用def語句來創建,標題行由def關鍵字,函數的名字,以及參數的集合(如果有的話)組成。
  • Python程式設計師最常犯的10個錯誤,你中招了嗎?
    >>>常見錯誤4:錯誤理解Python中變量的作用域Python變量作用域遵循LEGB規則,LEGB是Local,Enclosing,Global,Builtin的縮寫,分別代表本地作用域、封閉作用域、全局作用域和內置作用域,這個規則看起來一目了然。
  • Python Global和Nonlocal的用法
    解釋global總之一句話,作用域是全局的,就是會修改這個變量對應地址的值。global 語句是一個聲明,它適用於整個當前代碼塊。 這意味著列出的標識符將被解釋為全局變量。 儘管自由變量可能指的是全局變量而不被聲明為全局變量。global語句中列出的名稱不得用於該全局語句之前的文本代碼塊中。
  • Python生成器函數概述:運用實例分解說明機制
    鑑於Python入門並不難,非常容易就可寫出真正能夠運作的代碼(比如迭代一列數值,計算以及/或列印一些數值),一些Python初學者和粗心的程式設計師可能沒有意識到該語言建立在procrastination,也即延遲計算的概念之上。對使用過編譯語言(如C++)的人而言,這種根植於該語言本身的鬆散性,或者說惰性,可能有點陌生。
  • 閉包,閉包
    一、閉包的定義:閉包是關係的一種特殊運算,運算結果依然是關係。
  • 利用python計算函數與x軸之間的面積
    用數學表達式表示出來就是:也就是求解任意一個函數的絕對值與x軸之間構成的面積,我們以函數sin(x)為例(因為函數sin(x)便於對計算結果進行檢驗),如圖所示:我們用積分的定義來計算,積分就是將函數分成無數的小段,然後對每一小段進行求和處理。
  • 高中數學《函數的概念》說課稿
    三、說教學目標根據以上對教材的分析以及對學情的把握,我制定了如下三維教學目標:(一)知識與技能理解函數的概念,能對具體函數指出定義域、對應法則、值域,能夠正確使用「區間」符號表示某些函數的定義域、值域。
  • python入門教程NO.8 用python寫個存款利息計算器
    本文涉及的python基礎語法為def函數,return,函數的各參數示例,匿名函數等函數初識函數是一段組織好的\ 可重複使用的\ 用來實現特定功能的代碼塊。以上面的代碼為例:定義函數時的參數 x,y 為形參,調用函數時傳入的參數 5 , 2為實參。函數的參數關鍵字參數的應用示例必備參數的應用示例默認參數的應用示例不定長參數的應用示例python 使用 lambda 來創建匿名函數lambda只是一個表達式,函數體比def簡單很多。
  • 微積分之第二坑:函數定義域缺失
    微積分之第二坑:函數定義域缺失微積分學習中除了對於抽象函數重視不夠導致對函數的內涵和外延無法弄清楚造成後續很多概念產生混淆之外,最為低級的「坑」則是不關注函數的定義域。我們知道在中學期間學習的都是基本初等函數:常數函數、冪函數、對數函數、指數函數、三角函數和反三角函數,這些函數的定義域都是自然定義域,大家都很清楚,而對於初等函數來說,則增加了函數的四則運算和複合運算,這需要對和函數、差函數以及其他關於函數的四則運算、混合運算構成的新的函數的自變量的範圍進行界定,也就是求定義域。
  • python基礎課程 第5章 奇妙的內建函數
    今天我們來講講 python 的常用內建函數,以便於大家在日常編程過程中遇到類似的場景可以直接拿來使用,不用再重複自己了。python 內建函數(python自帶的函數) 數量加起來大概有70多個,今天我們主要講常用的一些,至於更多的內容可以在以後的基礎教程裡慢慢學到。
  • python:pop函數詳解 - 二進位01
    pop函數詳解今天我為大家講解python中pop函數的使用。#簡介——pop()函數是python解釋器的內置方法,可作用於列表,字典。用法說明——在builtins.py中找到pop函數。列表:L.pop([index]) -> item -- remove and return item at index (default last).
  • Python 爬蟲面試題 170 道:2019 版
    54.如果當前的日期為 20190530,要求寫一個函數輸出 N 天后的日期,(比如 N 為 2,則輸出 20190601)。55.寫一個函數,接收整數參數 n,返回一個函數,函數的功能是把函數的參數和 n 相乘並把結果返回。56.下面代碼會存在什麼問題,如何改進?
  • python動態添加類對象成員:變量、函數
    pythonyu'yan那麼下面就以下圖中的代碼為例,為大家演示如何給python中的類對象動態的添加變量給類對象動態添加方法/函數注意:初學者在理解以下內容之前,需清楚python類中 self 參數的含義和作用,可自行搜索查閱:Python中self的用法!
  • 高中函數不知道怎麼學?函數定義域,值域,單調性求法最全總結
    老師給出大家一個簡單學習技巧,拿到新的知識和考點,一定不要著急,要從基本都概念下手,這樣才能找到考點之間的區別,拿下這些考點。這次課程咱們來講一下函數的單調性,從單調性開始教你學函數。老師會通過知識點,解題思路,例題詳解來讓同學們輕鬆搞定高中函數知識。
  • 高一數學第一次月考考點匯總之基本函數定義域詳解
    基本概念函數我們初中的時候就接觸過函數,這個相信孩子們都不陌生。如一次函數,二次函數,反比例函數,正比例函數,等等。那麼到底什麼是函數?高中的函數和初中的函數有區別嗎?但是高中我們要這樣寫:f(x)=4x+5或者g(x)=4x+5……即高中的函數是字母(自變量)=表達式的格式表示即可。但是函數的概念和初中一樣,即每個自變量都有唯一的值和其相對應,函數是一一對應的關係。
  • 函數定義域、值域方法總結
    (一)求函數定義域1、函數定義域是函數自變量的取值的集合,一般要求用集合或區間來表示;2、常見題型是由解析式求定義域
  • python高階函數:map、filter、reduce的替代品
    什麼是高階函數?高階函數是一種將函數作為參數,或者把函數作為結果返回的函數,map函數、sorted函數就是高階函數的典型例子。map函數在小編以前的文章中做過相應的知識分享。sorted函數是python的內置函數,它的可選參數key用於提供一個函數,它可以將函數應用到各個元素上進行排序。
  • Python零基礎入門教程,如何使用函數?
    大綱函數語法格式及調用參數:默認值、元組和字典可變參數的使用全局變量和局部變量作用域,局部變量如何升級為全局變量函數是可重複使用的,實現單一功能的代碼塊。可以把項目中某一功能想像成積木模型,函數是組成模型的大大小小積木塊。