1. 程式人生 > >Python變數的理解與記憶體管理

Python變數的理解與記憶體管理

Python變數與記憶體管理

  –與C語言中的變數做對比,更好的理解Python的變數。

變數

變數在C語言中
  全域性變數:其存放在記憶體的靜態變數區中。
  區域性變數:程式碼塊中存放在記憶體的程式碼區當中,當被呼叫後存放在記憶體棧區。

而Python的變數儲存,則是使用類似堆的方式管理記憶體,由Python內部機制統一分配回收記憶體。

Python中的變數與變數儲存–引用與物件

  Python作為OOP(面向物件)程式設計,一直信奉著一個信條,就是萬物皆物件。
  所謂物件,它可以看成經過一系列的抽象以後,將具有共性的一類物體進行例項化(具象化)的個體,就如同我們每個人就是人類裡面的一個物件。

class A():
    name = "123"
    def __init__(self):
        pass

    def funa(self):
        pass
def funa():
    pass

if __name__ == "__main__":
    Fun = funa
    Variable = 1
    ClassA = A()
    ListA = [1,2,3]
    DictA = {'d':1,'e':2}
    TupleA = (1,2,3)
    Str = "python"
    print(type(Fun))
    print(type(Variable))
    print(type(ClassA))
    print(type(ListA))
    print(type(DictA))
    print(type(TupleA))
    print(type(Str))

輸出的是:
class ‘function’
class ‘int’
class ‘main.A’
class ‘list’
class ‘dict’
class ‘tuple’
class ‘str’

  很明顯,Python中不管是基礎資料型別,類,函式,所有的一切都是作為一個類的物件儲存在記憶體,也可以單純的看做一個值。

  而Python的變數就是作為一個引用,讀取物件所儲存的資訊,與C面向過程所不同,Python變數即物件的引用,通俗來說就是指向值的名稱。
  變數即物件的引用

  所以Python的變數只是不過對於一塊指定記憶體的引用,也即對物件的引用,或者稱為指向值的名稱,相對於全域性變數,區域性變數的賦值只是引用另一塊記憶體。C語言中一個變數代表一塊特定的記憶體,而Python不像C語言,可以看成資料已經存放在記憶體之中了,被Python的變數對記憶體進行引用。即使變數不存在了,記憶體裡值也不會受到任何影響。

if __name__ == "__main__":

    a = 1
    b = 2
    print(id(a))
    print(id(b))

    a = b
    print(id(a))
    print(id(1))
    print(id(2))

    sys.exit(0)

輸出的是:
10919424
10919456
10919456
10919424
10919456
  從輸出結果來看,很明顯同一塊記憶體資料其實是可以被多個變數引用,且常量和變數的記憶體地址是相對應的。
  Python記憶體儲存方式
 

def funa():
    a = 1
    print(id(a))

if __name__ == "__main__":

    a = 1
    funa()
    print(id(a))
    a = 2
    print(id(a))
    funa()

    sys.exit(0)

輸出結果:
10919424
10919424
10919456
10919424

  從輸出結果可以看出,若是當全域性變數和區域性變數的數值一致時,其對應的記憶體地址是一致的,當全域性變數被賦予其他值時,其記憶體地址發生改變,而區域性變數未有變化。
  
  
  總結:Python變數的定義和賦值是同時進行的,Python的全域性變數和區域性變數的定義宣告時,是基於記憶體已有資料的基礎上,為變數分配地址進行引用,變數即物件的引用,而不是分別分配一塊記憶體進行賦值,所以變數不進行賦值的話就會出現未定義的錯誤,,這時就會出現一個問題,這將會造成一個問題就是物件和資料將會越來越多,會消耗很大的記憶體空間,這時將會啟動Python的垃圾回收機制,當某一段記憶體塊的引用計數為0時進行回收,這個是後話了。

變數的作用域—看不見的字典

  C語言中每一對大括號作為一個程式碼塊,if,for,while,switch語句是可以加上大括號的作為一個塊級作用域,for,while()語句在括號中定義的變數是包含在大括號裡面的,就是包含在大括號的作用域裡,而每一個程式碼塊就是一個區域性的作用域,所有程式碼塊內部變數優先順序大於程式碼塊外的同名變數。
  
  Python的作用域,就如同是Python的基礎型別中的一部字典,在這部字典裡記錄著值(物件)與指向值的名稱(變數),不同的作用域組成了不同的字典,而Python中能改變變數作用域的關鍵字只有class,def,lamba,所以在Python的關鍵語句(if,for,while…)中是不進行作用域的劃分的,所以在(if,for,while…)語句對變數進行賦值,其變數的作用域可以被外部所引用。
  並且Python不存在塊級作用域,在巢狀作用域中會生成作用域鏈,由內到外,引用時優先選取內部同名變數。
  在類與例項的作用域中
  類與例項的作用域

class A():
    name = 'x'
    what = 'xx'
    print('A name id =',id(name))
    def __init__(self,name,age):
        self.name = 'xxx'
        self.age = 18

    def set(self):
        self.name = 'xxx'
        self.age = 18
        global name
        name = 'xx'
        print('set global name = ',id(name))#與A.what的記憶體地址相同
        print('set.name = ',self.name)#作用域是對於變數而言的而不是記憶體而言

if __name__ == "__main__":
    a = A('xx',18)
    a.set()
    print('A id = ', id(A))
    print('a id = ', id(a))
    print('A.name = ',id(A.name))
    print('a.name id = ',id(a.name))
    print('A.what id = ', id(A.what))
    print('a.what id = ',id(a.what))
    print('A.set id = ', id(A.set))
    print('a.set id = ', id(a.set))
    sys.exit(0)

輸出的是:
A name id = 140654891768216
set global name = 140654890720536
set.name = xxx
A id = 20336920
a id = 140654890787168
A.name = 140654891768216
a.name id = 140654890720704
A.what id = 140654890720536
a.what id = 140654890720536
A.set id = 140654690844744
a.set id = 140654891845576

  所以,作用域是對於變數而言的而不是記憶體而言,類與例項的作用域也是巢狀的
  
  Python變數作用域
  參考LEGB法則:
  Local(本地作用域)–>Enclosing(閉包作用域)–>Global(全域性作用域)–>Built-in(內建作用域)
  函式內部–>巢狀函式內部–>模組內部–>Python內建
  LEGB法則: 當在函式中使用未確定的變數名時,Python會按照優先順序依次搜尋4個作用域,以此來確定該變數名的意義。首先搜尋區域性作用域(L),之後是上一層巢狀結構中def或lambda函式的閉包作用域(E),之後是全域性作用域(G),最後是內建作用域(B)。按這個查詢原則,在第一處找到的地方停止。如果沒有找到,則會發出錯誤。
  變數作用域在定義時已經設定好,與呼叫的位置無關。

name ="???"
def funa():
    print(name)

def funb():
    name = "123"
    funa()

if __name__ == "__main__":

    funa()
    sys.exit(0)

輸出的是:
???
  所以變數的作用域與是否呼叫無關,在變數定義時所處作用域已經設定完成。
  

變數的生命週期—只要被需要便存在

  C語言的區域性變數是在函式呼叫完畢後進行自動銷燬,釋放棧區。
  
   而基於Python儲存方式的特殊性,所以變數在函式呼叫完畢之後,並未立刻銷燬,對於Python的變數和變數所引用的物件,是使用類似堆的方式管理記憶體,由Python內部機制統一分配回收記憶體,當記憶體的某一物件或者變數的引用計數為0時則由Python的記憶體管理機制收回記憶體,或者對物件手動del掉物件以釋放記憶體,不過del掉的物件不影響物件中依然被外部變數有引用的值。
  變數生命週期
  

class A():
    def __init__(self):
        self.a = 123
    def funa(self):
        c = a+1
        print(id(c))
variable = None
classA = None

def initA():

    global classA
    global variable
    e = A()
    variable = e.a
    classA = e
    print(id(e))
    print(id(e.a))

if __name__ == "__main__":

    a = 1
    e = None
    initA()
    print(id(classA))
    print(id(variable))
    del classA
    #print(id(classA))
    print(id(variable))

    sys.exit(0)

輸出結果是:
39697096
1409448784
39697096
1409448784
1409448784
   當函式被呼叫完,只要類例項還被引用,那麼類例項依然存在類似c++的new,當del物件時,不影響物件還在被外部變數引用的值。
   
  當我們若是del掉classA後,再輸入print(id(classA)),會出現如下錯誤:

Traceback (most recent call last):
  File "C:/Users/Administrator/PycharmProjects/pysidetest/demo.py", line 69, in <module>
    print(id(classA))
NameError: name 'classA' is not defined