對象引用、可變性和垃圾回收

 

對象的標註、引用、別名

Python變量相似於Java中的引用式變量,所以最好把他們理解爲附加在對象上的標註。python

Python賦值語句應該始終先讀右邊。算法

對象在右邊建立或獲取,再此以後左邊的變量纔會綁定到對象上,這就像爲對象貼上標註。app

變量只不過是標註,因此沒法阻止爲對象貼上多個標註。貼的多個標註,就是別名。 ide

>>> f1 = {'name':'Li','age':19}
>>> f2 = f1    # f2是f1的別名
>>> f2 is f1
True

每一個變量(object)都有標識(identity)、類型(type)和值(value)。函數

對象一旦建立,它的標識毫不會變。能夠把標識理解爲對象在內存中的地址。spa

is運算符比較兩個對象的標識,id()函數返回對象標識的整數表示,==運算符比較兩個對象的值(對象中保存的數據)。code

 對+= 或 *= 所作的增量賦值來講,若是左邊的變量綁定的是不可變對象,會建立新對象,若是是可變對象,會就地修改。對象

 

元組的相對不可變性

元組的相對不可變性:blog

元組與多數Python集合(列表、字典、集)同樣,保存的是對象的引用。進程

若是引用的元素是可變的,即使元組自己不可變,元素依然可變。

 

copy、deepcopy

淺複製:(複製最外層容器,副本中的元素是源容器中元素的引用)

複製列表最簡單的方式:

(1)使用構造方法

(2)使用 [ :]

>>> l1 = [1,2,3,4]
>>> l2 = list(l1)
>>> l3 = l1[:]

深複製:副本不共享內部對象的引用

class Bus:
    def __init__(self,passengers=None):
        if passengers is None:
            self.passengers = []
        else:
            self.passengers = list(passengers)
            
    def pick(self,name):
        self.passengers.append(name)
        
    def drop(self,name):
        self.passengers.remove(name)

import copy

bus1 = Bus([1,2,3,'David','Tom'])
bus2 = copy.copy(bus1)
bus3 = copy.deepcopy(bus1)

print(id(bus1),id(bus2),id(bus3))
print(id(bus1.passengers),id(bus2.passengers),id(bus3.passengers))

bus1.drop('David')
bus1.pick(998)

print(bus1.passengers)
print(bus2.passengers)
print(bus3.passengers)
View Code

 

函數參數傳遞模式

函數參數做爲引用時:

Python惟一支持的參數傳遞模式:共享傳參(call by sharing)

共享傳參:指函數各個形式參數得到實參中各個引用的副本。(函數內部的形參是實參的副本)

def f(a,b):
    a += b
    return a
    
a = 1
b = 2
c = f(a,b)
>>> print(c)
3
>>> print(a,b)
1,2

# ========

def f(a,b):
    a += b
    return a
    
a = [1,2]
b = [3,4]
c = f(a,b)
>>> print(c)
[1, 2, 3, 4]
>>> print(a,b)
[1, 2, 3, 4] [3, 4]

避免使用可變類型做爲參數的默認值

bus_team = ['Sue','David','Pat','Maria']
bus = Bus(bus_team)
bus.drop('David')
bus.drop('Pat')
>>> bus_team
['Sue','Maria']

在類中直接把參數賦值給實例變量等於爲參數對象建立別名。

class Bus:
    def __init__(self,bus_team):
        if bus_team is None:
            self.bus_passengers = []
        else:
            self.bus_passengers = bus_team

# =============

class Bus:
    def __init__(self,bus_team):
        if bus_team is None:
            self.bus_passengers = []
        else:
            self.bus_passengers = list(bus_team)

 

垃圾回收機制

del和垃圾回收:

del語句刪除名稱,而不是對象。

del命令可能會致使對象被看成垃圾回收,可是僅當刪除的變量保存的是對象的最後一個引用,或者沒法獲得對象時。

在Cpython中,垃圾回收使用的主要算法是引用計數。實際上,每一個對象都會統計有多少引用指向本身。

當引用計數歸零時,對象當即就被銷燬。Cpython會在對象上調用__del__方法,而後釋放分配給對象的內存。

>>> import weakref 
>>> s1 = {1,2,3}
>>> s2 = s1
>>> ender = weakref.finalize(s1,bye)   # 註冊一個回調函數
>>> ender.alive
True
>>> del s1
>>> ender.alive
True
>>> s2 = '123'
Gone with the wind
>>> ender.alive
False

弱引用:

正由於有引用,對象纔會在內存中存在。

弱引用不會增長對象的引用數量。

引用的目標對象稱爲所指對象(referent),弱引用不會妨礙所指對象被看成垃圾回收。

WeakValueDictionary 類實現的是一種可變映射,裏面的值是對象的弱引用。

被引用的對象在程序中的其餘地方被看成垃圾回收後,對應的鍵會自動從WeakValueDictionary中刪除。

class Cheese:
    def __init__(self,kind):
        self.kind = kind

>>> import weakref
>>> stock = weakref.WeakValueDictionary()
>>> catalog = [Cheese('Red Leicester'),Cheese('Tilsit'),Cheese('Parmesan')]
>>> for cheese in catalog:
            stock[chess.kind] = cheese

>>> sorted(stock.keys())
['Parmesan','Red Leicester','Tilsit']
>>> del catalog
>>> sorted(stock.keys())
['Parmesan']
>>> del cheese
>>> sorted(stock.keys())
[]

▲ for循環中的變量cheese是全局變量,除非顯式刪除,不然不會消失。

weakref 模塊還提供了WeakSet 類,若是一個類須要知道全部實例,一種好的方案是建立一個WeakSet 保存實例引用。

常規Set實例永遠也不會被垃圾回收,由於類中有實例的強引用。而類存在的時間與Python進程同樣長,除非顯式刪除類。

弱引用的侷限:

基本的list和dict實例不能做爲所指對象,可是他們的子類能夠。set實例和用戶自定義類型能夠做爲所指對象。

同時int和tuple實例不能做爲弱引用的目標,甚至它們的子類也不行。

class Mylist(list):
    ...

a_list = Mylist(range(10))
wref_to_a_list = weakref.ref(a_list)
相關文章
相關標籤/搜索