熱線電話:13121318867

登錄
首頁精彩閱讀深入理解Python內部函數和閉包,和3個應用場景
深入理解Python內部函數和閉包,和3個應用場景
2021-02-19
收藏

來源:麥叔編程

作者:麥叔

本文以內部函數為主線,深入講解內部函數和閉包的應用場景和原理,學會后你的Python水平會再上一個臺階,對工作面試或實戰應用都會很有幫助。

深入理解Python內部函數和閉包,和3個應用場景

本文包括:

  1. 函數是一等公民
  2. 內部函數定義
  3. 閉包和nonlocal關鍵詞
  4. 應用場景 - 封裝
  5. 應用場景 - 函數生成器
  6. 函應用場景 - 裝飾器
  7. 閉包實現原理

閱讀到最后可以獲得本文PDF資料和源代碼下載,建議收藏。

一、函數是一等公民

Python是面向對象的編程語言,對象是Python的一等公民,我們常用的字符串str,整數int,和其他變量都是對象。

函數也是對象,所以也是一等公民,這就意味著它和變量一樣

  1. 可以作為參數被傳遞
  2. 可以在函數內部定義
  3. 可以作為函數返回值
  4. 函數可以賦值給變量
def say_hello():  print('hello')

print(say_hello) def say_something(some_func):  for _ in range(3):
 some_func()

say_something(say_hello)

執行結果:

 say_hello at 0x7ff3d35b9160>
hello
hello
hello 

二、內部函數

把函數的內部定義函數,就是內部函數(有點像廢話,但就那么個意思)。

def outter():     print('我是外部函數')
    def inner():         print('我是outter的內部函數')
    print('調用內部函數')
    inner()
    print('我再次調用內部函數,自己家的想用就用,隨時用')
    inner()
    print('還可以返回給大家共用')
    return inner  #調用外部函數,并接受返回值 func = outter() #調用outter返回的內部函數
print('在外部調用內部函數')
func()

注意: 調用的時候加小括號inner(),作為參數或者返回值的時候不加小括號inner,是引用這個函數對象。

執行結果:

我是外部函數
調用內部函數
我是outter的內部函數
我再次調用內部函數,自己家的想用就用,隨時用
我是outter的內部函數
還可以返回給大家共用
在外部調用內部函數
我是outter的內部函數

三、閉包

如果內部函數只是把函數定義在函數的內部,那就沒有多大意思了,它還有一個很大的特點,正因為這個特點,它才被稱為閉包clsure。

學過JavaScript的非小白同學可能會對這個概念很熟悉。

內部函數還有一個很重要的特性:

  1. 可以訪問它所屬的外部函數的局部變量,這些變量被稱為nonlocal,或者enclosing變量
  2. 可以攜帶這些nonlocal變量,讓它們不會被回收

所以說Python中的閉包就是內部函數,準確點是使用了nonlocal變量的內部函數。

import random def create_room():     room_no = random.randint(1100) 
    print(f'我創建了房間號:{room_no}')

    def toilet():         print(f'我是{room_no}的內部廁所')
    print('上廁所')
    toilet()
    print('我再次上廁所,自己家的想用就用,隨時用')
    toilet()
    print('還可以共享給大家共用')
    return toilet #調用外部函數,并接受返回值 toilet = create_room() #調用outter返回的內部函數
print('在外部使用內部廁所')
toilet()

print('在外部再次使用內部廁所')
toilet()

運行結果:

我創建了房間號:52
上廁所
我是52的內部廁所
我再次上廁所,自己家的想用就用,隨時用
我是52的內部廁所
還可以共享給大家共用
在外部使用內部廁所
我是52的內部廁所
在外部再次使用內部廁所
我是52的內部廁所
  • 在調用一個create_room的時候臨時生成了房間號,一個局部變量room_no。
  • 在內部函數toilet中可以直接訪問外部函數的局部變量,這是內部函數的特性。
  • 局部變量room_no本來在函數執行完就釋放的,但由于內部函數toilet引用了它就不會被釋放了,在外部調用的時候仍然可以引用到。這就形成了閉包。

說的這么玄乎,其實就是內部函數使用了外部函數的局部變量,所以局部變量被內部函數給封存了,也就不會釋放了。

四、nonlocal關鍵詞

內部函數也可以改寫外部函數的變量值,但需要使用nonlocal關鍵詞聲明這是外部的變量。

回憶一下:函數內部修改全局變量,需要使用global關鍵詞。

import random def create_room():     room_no = random.randint(1100)
    print(f'我創建了房間號:{room_no}')

    def toilet():         nonlocal room_no
        room_no = random.randint(1100)
        print(f'我是{room_no}的內部廁所')
    
    print('上廁所')
    toilet()
    print(f'房間號:{room_no}')
    print('我再次上廁所,自己家的想用就用,隨時用')
    toilet()
    print(f'房間號:{room_no}')
    print('還可以共享給大家共用')
    return toilet #調用外部函數,并接受返回值 toilet = create_room() #調用outter返回的內部函數
 print('在外部使用內部廁所')
toilet()

print('在外部再次使用內部廁所')
toilet()

使用nonlocal在內部函數改變外部變量room_no的值,所以每次上廁所,都會改變房間號(很神奇的房間):

我創建了房間號:39
上廁所
我是43的內部廁所
房間號:43
我再次上廁所,自己家的想用就用,隨時用
我是66的內部廁所
房間號:66
還可以共享給大家共用
在外部使用內部廁所
我是52的內部廁所
在外部再次使用內部廁所
我是29的內部廁所

其實內部函數就這么點東西了(后面再說它的實現原理),現在來看到底有什么實實在在的用處。

下面來說3個應用場景:

五、應用場景 - 封裝

寫在內部是因為只有在內部才有用,外部根本不需要,也不想讓他們使用,就像上面的內部廁所的例子,實際上是不可能在外面使用的。這種場景叫做封裝。

import random def create_room():     room_no = random.randint(1100)
    print(f'我創建了房間號:{room_no}')

    def toilet():         print(f'歡迎進入{room_no}的VIP廁所')
        print('沖水')
        print('請君入廁')
        print('洗手')
        print('熱毛巾')
        print('歡迎下次光臨')

    print('上廁所')
    toilet()
    print('上廁所')
    toilet()
    print('上廁所')
    toilet() #調用外部函數,并接受返回值 create_room()
  • 上廁所是create_room所獨有的配方,不希望外面使用
  • 上廁所的過程獨有配方是比較復雜的,有必要封裝到函數內,否則每次上廁所都要重復這些代碼

再總結一下:

  1. 封裝一方面不希望對外暴露函數
  2. 也為了方便內部的重用

六、應用場景 - 函數生成器

內部函數可以方便的生成新的函數,看這個例子:

def team_maker(type, level, temperature):     '''
    type: 品種,如綠茶,紅茶
    level: 等級,特級,一級,二級
    temper: 溫度
    '''     def tea(water):         print('正在沏茶中')
        print(f'{type},{level}{temperature}')
        print(f'{water}毫升給您沖好了')
    return tea #創建符合我口味的沏茶函數 mytea = team_maker('綠茶''特級''66.6')
 #創建符合她口味的沏茶函數 herteam = team_maker('紅茶''一級''88.8')

print('我們來一杯')
mytea(500)
herteam(300)
print('多喝點')
mytea(800)
herteam(600)
print('完了,我喝醉了...,因為有她')
  • 沏茶需要傳入多個參數,有點麻煩,而每個人的口味相對比較固定
  • 我們用內部函數創建了一個符合我口味的沏茶函數,以后調用這個函數就行了。我也給她創建了一個符合她口味的沏茶函數。
深入理解Python內部函數和閉包,和3個應用場景

運行結果:

我們來一杯 正在沏茶中 綠茶,特級, 66.6 500毫升給您沖好了 正在沏茶中 紅茶,一級, 
88.8 300毫升給您沖好了 多喝點 正在沏茶中 綠茶,特級, 
66.6 800毫升給您沖好了 正在沏茶中 紅茶,一級, 88.8 600毫升給您沖好了 完了,我喝醉了... 

七、應用場景 - 裝飾器

裝飾器對Python至關重要。這也是內部函數的主要使用場景。

寫到這里忽然有點累了,我想起來我有兩篇還不錯的裝飾器的文章,直接拿來看吧。我就不重復碼字了。

結合內部函數的文章和裝飾器的文章,應該會通透了。

這兩篇文章見本文底部相關閱讀前兩篇。

深入理解Python內部函數和閉包,和3個應用場景

八、閉包實現原理

閉包攜帶了外部函數的變量,所以可以訪問這些變量,而這些變量也不會被釋放。具體是怎么實現的呢?

答案就1個字:__closure__屬性。Python給內部函數添加了這個屬性來攜帶內部函數用到的外部函數中的變量。

import random def create_room():     room_no = random.randint(1100)
    print(f'我創建了房間號:{room_no}')

    def toilet():         print(f'我是{room_no}的內部廁所')
    return toilet


print('調用外部函數')
toilet = create_room()

print('打印一下toilet函數的變量,其中有一個是__closure__')
print(dir(toilet))
print('__closure__是一個包含它攜帶的變量的元組')
print(toilet.__closure__)
print('__closure__元組里是cell,通過cell_contents可以訪問所攜帶的變量值')
print(toilet.__closure__[0].cell_contents)

執行結果:

import random def create_room():     room_no = random.randint(1100)
    print(f'我創建了房間號:{room_no}')

    def toilet():         print(f'我是{room_no}的內部廁所')
    return toilet
print('調用外部函數')
toilet = create_room()

print('打印一下toilet函數的變量,其中有一個是__closure__')
print(dir(toilet))
print('__closure__是一個包含它攜帶的變量的元組')
print(toilet.__closure__)
print('__closure__元組里是cell,通過cell_contents可以訪問所攜帶的變量值')
print(toilet.__closure__[0].cell_contents)

數據分析咨詢請掃描二維碼

若不方便掃碼,搜微信號:CDAshujufenxi

數據分析師資訊
更多

OK
客服在線
立即咨詢
日韩人妻系列无码专区视频,先锋高清无码,无码免费视欧非,国精产品一区一区三区无码
客服在線
立即咨詢