10 - 函數嵌套-做用域-閉包-LEGB-函數銷燬

1 函數嵌套

        一個函數中存在另一個函數(定義/調用),這種方式咱們稱之爲函數嵌套。因此:函數的嵌套主要分爲嵌套調用,以及嵌套定義編程

函數的嵌套調用
def max2(a,b):   # 判斷兩個變量的最大值
    return a if  a > b else b
 
def max4(a,b,c,d):  # 判斷四個變量的最大值
    res1 = max2(a,b)  # 函數的嵌套調用
    res2 = max2(res1,c)
    res3 = max(res2,d)
    print(res3)
 
max4(10,100,21,99)


函數的嵌套定義
def func1():
    print('from func1')
    def func2():
        print('from func2')
        def func3():
            print('from func3')
        func3()  # 只有在func2中才能調用內部定義的函數func3
    func2()
 
func1()

注意:在函數的內部定義函數,只能在函數內部進行調用,在其餘地方是沒法進行調用,強行調用就會提示NameError異常,因此說函數是有可見範圍的,這就涉及到了做用域了閉包

2 做用域

        一個標識符的可見範圍,叫作標識符的做用域。通常常說的是變量的做用域。根據做用的範圍主要分爲全局做用域局部做用域app

  • 全局做用域:在整個程序運行環境中均可見
  • 局部做用域:在函數、類的內部可見,而且使用範圍不能超過所在的局部做用域(好比在函數內部定義了一個變量x,我在全局使用變量x是不行的。)
x = 1   # 全局變量
def outer():
    def inner():
        y = 100   # 局部變量
        print(x) 
    inner()

outer()
print(y)
  • 全局變量x在全局生效,因此內部函數inner是能夠打印x的
  • 局部變量y只在inner內部生效,因此在全局print(y) 是沒法調用局部變量y的

觀察下面的例子:dom

x = 1
def outer():
    def inner():
        x += 1
        return x
    inner()
outer()

代碼是從上到下執行的,所欲這樣寫也沒什麼毛病,可是這裏這個例子是沒法執行的,爲何呢?編程語言

  1. x做爲全局變量,在inner內部是可見的
  2. 在定義函數的階段,Python的函數是做爲一個總體一塊兒被解釋的
  3. inner函數在解釋時,解釋器發如今inner內部對x進行了定義(x += 1),那麼它就不會在調用全局變量x,而是標識x是局部定義的變量
  4. 而在執行x+=1的時候,inner內部的x尚未被定義,因此會提示x在定義前被執行了。(x += 1 --> x = x + 1 ,預先求 x + 1 時提示的)。

如何解決呢?有兩種方法:更換變量名稱、聲明當前變量非本地變量(global)函數

x = 1
def outer():
    def inner():
        y = x + 1    # 這裏定義的y是局部變量,而x來自於全局變量
        return y
    return inner()
print(outer())

2.1 global關鍵字

咱們經過在函數內部使用global關鍵字來聲明一個變量不是局部變量,而是一個全局變量。ui

def outer():
    def inner():
        global x   # 在函數內部聲明一個全局變量,全局不存在時新建全局變量x,全局變量x存在時,則使用全局變量x
        x = 10   # 修改全局變量x的值
    inner()

outer()
print(x)

雖然全局變量x,在全局沒有被定義,可是因爲在函數內部使用了global關鍵字,因此x就變成了全局變量了。使用了global關鍵字,那麼以前的例子就能夠進行以下修改了指針

x = 1
def outer():
    def inner():
        global x  # 使用全局變量x
        x += 1  # 這裏的x是全局變量,那麼對x的修改必然會做用域全局
        return x
    inner()
outer()
print(x) # 2   , 在函數內部把全局變量x給修改了!!!

針對global的總結:code

  1. 外部做用域變量在內部做用域是可見的,可是不要在內部函數中直接使用或者修改,由於函數的目的就是爲了封裝,儘可能與外界隔離。
  2. 若是函數須要使用外部全局變量,請儘可能使用函數的形參定義,在調用時傳遞實參來使用
  3. 建議不要使用global

3 閉包

        在不少編程語言中都存在閉包的概念,那什麼是閉包呢?閉包其實就是一個概念,出如今嵌套函數中,指的是:內層函數引用到了外層函數的自由變量,就造成了閉包

自由變量:未在本地做用域中定義的變量,好比在嵌套函數的外層定義的變量(非全局變量),對內層來講,這個變量就叫作自由變量。

def outer():
    c = [1]
    def inner():
        c[0] = 1
        return c
    return inner()
a = outer()
print(a)

注意:上面這個例子比較特殊,首先它是一個閉包,在inner函數內引用了外層函數的自由變量C。由於這裏的c是一個引用類型,咱們能夠直接經過c來操做c中的元素,可是沒辦法對c自己進行修改,即c += [1,3]。看似是列表拼接,可是它會從新對c進行聲明,這就引起了以前的問題,內部函數inner沒有定義c,因此會報錯!因此當c不是引用類型的話,咱們就沒辦法修改了嗎?固然不是,可使用global把c聲明爲全局變量,可是這就不是閉包了,因此這裏就須要使用nonlocal了(python 3 特有)。
疑問?咱們都說函數執行完畢後,函數的內部變量將會被回收,這裏的outer執行完畢後,那麼變量c應該會被回收啊,爲何還能被內層的inner找到呢?這是由於在定義階段,解釋器解釋到inner函數時,因爲函數是做爲一個總體被解析的,因此解釋器知道在inner內部引用了外部的變量,因此在執行函數outer時,並不會回收已被內部函數inner引用的自由變量c。

3.1 nonlocal關鍵字

        使用了nonlocal關鍵字,將變量標記爲不在本地做用域定義,而在上一級局部做用域中定義,但不能是全局做用域中定義。

nonlocal只能用在嵌套函數的內部

def outer():
    c = 100
    def inner():
        nonlocal c  # 聲明不是本地的c(引用上級目錄的c)
        c += 200   # 對c進行修改
        return c
    print('內',c)   # 100
    c = 1000
    return inner

a = outer()
print('外',a)  # 1200

4 默認值的做用域

        在Python中,一切皆對象,函數也不列外,當咱們給函數定義默認值時,Python會把它存放在函數的屬性中,這個屬性值就伴隨這個函數對象的整個生命週期。

foo.__defaults__屬性查看函數的默認值屬性

In [77]: import random
    ...:
    ...: def add(x=set(),y=[]):
    ...:     x.add(random.randint(1,10))
    ...:     y.append(1)
    ...:     # print(x)
    ...:     # print(y)
    ...:
    ...: print(add(),id(add))
    ...: print(add.__defaults__)
    ...:
    ...: print(add(),id(add))
    ...: print(add.__defaults__)
None 2721081985904
({1}, [1])
None 2721081985904
({1, 10}, [1, 1])

        仔細查看輸出結果,發現函數地址沒有變,也就是說函數這個對象沒有變,可是咱們發現每次它的__default__屬性都會發生變化,這是爲何呢?這是由於sed和list的默認值都是引用類型,它們引用的都是函數在定義時定義的默認值中。 雖然函數執行完就釋放了內存空間,也是因爲引用類型,指向默認空間的指針沒了,可是已經在調用時改變了默認值空間的對象中的元素,因此在下一次再次調用時此時默認值空間的元素已經被改變了。因此當函數的默認值爲引用類型時,這點要特別的注意了
解決辦法:

  • 在定義時使用引用類型時,在函數內部使用前先進行copy。
  • 在定義函數時默認使用None值,在函數內判斷若是是None則開闢一個引用類型。

5 變量名解析原則LEGB

        變量的解析原則,也能夠理解爲變量的查找順序:

  • L(Local): 本地做用域、局部做用域的local命名空間。函數調用是建立,調用結束消亡
  • E(Enclosing): Python 2.2時引入嵌套函數,實現了閉包,這個就是嵌套函數的外部函數的命名空間
  • G(Global): 全局做用域,即一個模塊的命名空間。模塊被import時建立,解釋器退出時消亡
  • B(Build-in): 內置模塊的命名空間,生命週期從Python解釋器啓動時建立到解釋器退出時消亡。例如print函數、open函數等。

    變量查找的規則爲 L > E > G > B,即:先本地後嵌套再全局最後是內置函數中

6 函數的銷燬

全局函數:

  • 從新定義同名函數
  • del 語句刪除函數名稱,函數對象引用計數減1
  • 程序結束時

局部函數:

  • 從新在上級做用域定義同名函數
  • del 語句刪除函數名稱,函數對象的引用計數減1
  • 上級做用域銷燬時
相關文章
相關標籤/搜索