1. 程式人生 > >python閉包&深淺拷貝&垃圾回收&with語句

python閉包&深淺拷貝&垃圾回收&with語句

1. 閉包

  1、閉包概念

      1. 在一個外函式中定義了一個內函式,內函式裡運用了外函式的臨時變數,並且外函式的返回值是內函式的引用,這樣就構成了一個閉包

      2. 一般情況下,在我們認知當中,如果一個函式結束,函式的內部所有東西都會釋放掉,還給記憶體,區域性變數都會消失。

      3. 但是閉包是一種特殊情況,如果外函式在結束的時候發現有自己的臨時變數將來會在內部函式中用到,就把這個臨時變數繫結給了內部函式,然後自己再結束。

   2、閉包特點    

      1. 必須有一個內嵌函式

      2. 內嵌函式必須引用外部函式中的變數

      3. 外部函式的返回值必須是內嵌函式

#閉包函式的例項
def outer( a ):
    b = 10
    def inner():          # 在內函式中 用到了外函式的臨時變數
        print(a+b)        # 外函式的返回值是內函式的引用
    return inner

if __name__ == '__main__':
    demo = outer(5)
    demo()    # 15

# 在這裡我們呼叫外函式傳入引數5
# 此時外函式兩個臨時變數 a是5 b是10 ,並建立了內函式,然後把內函式的引用返回存給了demo
# 外函式結束的時候發現內部函式將會用到自己的臨時變數,這兩個臨時變數就不會釋放,會繫結給這個內部函式
# 我們呼叫內部函式,看一看內部函式是不是能使用外部函式的臨時變數
# demo存了外函式的返回值,也就是inner函式的引用,這裡相當於執行inner函式
閉包例項

  3、閉包中內函式修改外函式區域性變數 

    1、在基本的python語法當中,一個函式可以隨意讀取全域性資料,但是要修改全域性資料的時候有兩種方法:
        1) global 宣告全域性變數
        2) 全域性變數是可變型別資料的時候可以修改
    2、在閉包情況下使用下面兩種方法修改
        1)在python3中,可以用nonlocal 關鍵字宣告 一個變數, 表示這個變數不是區域性變數空間的變數,需要向上一層變數空間找這個變數。
        2)在python2中,沒有nonlocal這個關鍵字,我們可以把閉包變數改成可變型別資料進行修改,比如列表。

 2. 拷貝

   1、python的變數及其儲存

       python的一切變數都是物件,變數的儲存,採用了引用語義的方式,儲存的只是一個變數的值所在的記憶體地址,而不是這個變數的只本身

  2、淺copy與deepcopy

      1、淺copy:  不管多麼複雜的資料結構,淺拷貝都只會copy一層

      2、deepcopy :  深拷貝會完全複製原變數相關的所有資料,在記憶體中生成一套完全一樣的內容,我們對這兩個變數中任意一個修改都不會影響其他變數

3. Python垃圾回收機制 

  1、引用計數

    1. 原理

        1)當一個物件的引用被建立或者複製時,物件的引用計數加1;當一個物件的引用被銷燬時,物件的引用計數減1.
        2)當物件的引用計數減少為0時,就意味著物件已經再沒有被使用了,可以將其記憶體釋放掉。

    2. 優點

        引用計數有一個很大的優點,即實時性,任何記憶體,一旦沒有指向它的引用,就會被立即回收,而其他的垃圾收集技術必須在某種特殊條件下才能進行無效記憶體的回收。

    3. 缺點

        1)引用計數機制所帶來的維護引用計數的額外操作與Python執行中所進行的記憶體分配和釋放,引用賦值的次數是成正比的,
        2)這顯然比其它那些垃圾收集技術所帶來的額外操作只是與待回收的記憶體數量有關的效率要低。
        3)同時,因為物件之間相互引用,每個物件的引用都不會為0,所以這些物件所佔用的記憶體始終都不會被釋放掉。

  2、標記-清除

    1. 說明  

        1)它分為兩個階段:第一階段是標記階段,GC會把所有的活動物件打上標記,第二階段是把那些沒有標記的物件非活動物件進行回收。

        2)物件之間通過引用(指標)連在一起,構成一個有向圖

        3)從根物件(root object)出發,沿著有向邊遍歷物件,可達的(reachable)物件標記為活動物件,不可達的物件就是要被清除的非活動物件。

              根物件就是全域性變數、呼叫棧、暫存器。

        注:像是PyIntObject、PyStringObject這些不可變物件是不可能產生迴圈引用的,因為它們內部不可能持有其它物件的引用。

             

          1. 在上圖中,可以從程式變數直接訪問塊1,並且可以間接訪問塊2和3,程式無法訪問塊4和5
          2. 第一步將標記塊1,並記住塊2和3以供稍後處理。
          3. 第二步將標記塊2,第三步將標記塊3,但不記得塊2,因為它已被標記。
          4. 掃描階段將忽略塊1,2和3,因為它們已被標記,但會回收塊4和5。

    2、缺點

        1)標記清除演算法作為Python的輔助垃圾收集技術,主要處理的是一些容器物件,比如list、dict、tuple等

             因為對於字串、數值物件是不可能造成迴圈引用問題。

        2)清除非活動的物件前它必須順序掃描整個堆記憶體,哪怕只剩下小部分活動物件也要掃描所有物件。

   3、分代回收

      1. 分代回收是建立在標記清除技術基礎之上的,是一種以空間換時間的操作方式。

      2. Python將記憶體分為了3“代”,分別為年輕代(第0代)、中年代(第1代)、老年代(第2代)

      3. 他們對應的是3個連結串列,它們的垃圾收集頻率與物件的存活時間的增大而減小。

      4. 新建立的物件都會分配在年輕代,年輕代連結串列的總數達到上限時,Python垃圾收集機制就會被觸發

      5. 把那些可以被回收的物件回收掉,而那些不會回收的物件就會被移到中年代去,依此類推

      6. 老年代中的物件是存活時間最久的物件,甚至是存活於整個系統的生命週期內。

4. 上下文管理(with)

with open('/etc/passwd') as f:
    for line in f:
        print(line)
  # 這段程式碼的作用:開啟一個檔案,如果一切正常,把檔案物件賦值給f,然後用迭代器遍歷檔案中每一行,當完成時,關閉檔案;

  # 而無論在這段程式碼的任何地方,如果發生異常,此時檔案仍會被關閉。

&n