流暢的python讀書筆記-第八章-對象引用、可變性和垃圾回收

對象不是個盒子

clipboard.png

class Gizmo:
    def __init__(self):
        print('Gizmo id: %d' % id(self))

x = Gizmo()
print(x)

y = Gizmo() * 10
print(y)

print(dir())
❶ 輸出的 Gizmo id: ... 是建立 Gizmo 實例的反作用。
❷ 在乘法運算中使用 Gizmo 實例會拋出異常。
❸ 這裏代表,在嘗試求積以前其實會建立一個新的 Gizmo 實例。
❹ 可是,確定不會建立變量 y,由於在對賦值語句的右邊進行求值時拋出了異常。
 爲了理解 Python 中的賦值語句,應該始終先讀右邊。對象在右邊建立或獲取,在此以後左邊的變量纔會綁定到對象上,

標識、相等性和別名

longe = {'name': 'longe', 'born': 1993}
liang = longe
print(liang is longe)

print(id(liang), id(longe))

longe['balance'] = 950

print(liang)

## 冒充的longe信息

other  = {'name': 'longe', 'born': 1993, 'balance': 950}
print(other)

print(other is longe)

❶ liang 是 longe 的別名。
❷ is 運算符和 id 函數確認了這一點。
❸ 向 liang 中添加一個元素至關於向 longe 中添加一個元素。html

在那段代碼中,liang 和 longe 是別名,即兩個變量綁定同一個對象。
而 other 不是 longe 的別名,由於兩者綁定的是不一樣的對象。python

other 和longe 綁定的對象具備相同的值(== 比較的就是值),可是它們的標識不一樣。
  1. 每一個變量都有標識、類型和值。對象一旦建立,它的標識毫不會變;
  2. 你能夠把標識理解爲對象在內存中的地址。
  3. is 運算符比較兩個對象的標識;
  4. id() 函數返回對象標識的整數表示。

在==和is之間選擇

== 運算符比較兩個對象的值(對象中保存的數據),而 is 比較對象的標識。算法

  1. is 運算符比 == 速度快,由於它不能重載,因此 Python 不用尋找並調用特殊方法,而是 直接比較兩個整數 ID
  2. eq 方法,會考慮對象屬性的值。相等性測試可能涉及大量處理工做,例如,比較大型集合或嵌套層級深的結構時。

元組的相對不可變性

元組的不可變性實際上是指 tuple 數據結構的物理內容(即保存的引用)不可變,與引用的對象無關編程

  1. 元組的值會隨着引用的可變對象的變化而變。
  2. 元組中不可變的是元素的標識。內存地址
>>> t1 = (1, 2, [30, 40]) ➊
>>> t2 = (1, 2, [30, 40]) ➋
>>> t1 == t2 ➌
True
>>> id(t1[-1]) ➍
4302515784
>>> t1[-1].append(99) ➎
>>> t1
(1, 2, [30, 40, 99])
>>> id(t1[-1]) ➏
4302515784
>>> t1 == t2 ➐
False
基礎理解!!!仍是能夠的

默認淺複製

>>> l1 = [3, [55, 44], (7, 8, 9)]
>>> l2 = list(l1) ➊
>>> l2
[3, [55, 44], (7, 8, 9)]
>>> l2 == l1 ➋
True
>>> l2 is l1 ➌
False

然而,構造方法或 [:] 作的是淺複製(即複製了最外層容器,副本中的元素是源容器中
元素的引用)。若是全部元素都是不可變的,那麼這樣沒有問題,還能節省內存。緩存

爲任意對象作深複製和淺複製

import copy
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)


bus1 = Bus(['Alice', 'Bill', 'Claire', 'David'])
bus2 = copy.copy(bus1)
bus3 = copy.deepcopy(bus1)

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

bus1.drop('Bill')
print(bus2.passengers)

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

print(bus3.passengers)

❸ 審查 passengers 屬性後發現,bus1 和 bus2 共享同一個列表對象,由於 bus2 是
bus1 的淺複製副本。
❹ bus3 是 bus1 的深複製副本,所以它的 passengers 屬性指代另外一個列表。數據結構

注意,通常來講,深複製不是件簡單的事。若是對象有循環引用,那麼這個樸素的算法會進入無限循環

深複製

>>> a = [10, 20]
>>> b = [a, 30]
>>> a.append(b)
>>> a
[10, 20, [[...], 30]]
>>> from copy import deepcopy
>>> c = deepcopy(a)
>>> c
[10, 20, [[...], 30]]
深複製有時可能太深了。例如,對象可能會引用不應複製的外部資源或單例值。咱們能夠實現特殊方法 __copy__() 和 __deepcopy__(),控制 copy 和 deepcopy 的行爲

函數的參數做爲引用時

共享傳參指函數的各個形式參數得到實參中各個引用的副本。也就是說,函數內部的形參
是實參的別名。app

def f(a, b):
    a += b
    return a


a = [1, 2]
b = [3, 4]

print(f(a, b))
print(a, b)

這裏變量全都是引用,不管局部變量仍是全局.
因此上面案例中,a會變化函數

不要使用可變類型做爲參數的默認值

class HauntedBus:
    """備受幽靈乘客折磨的校車"""
    def __init__(self, passengers=[]):  #別使用這種可變類型 做爲默認參數
        self.passengers = passengers

防護性編程(對待可變類型)

class TwilightBus:
    """正常的校車"""

    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)


bus1 = TwilightBus(("sfs", 'sdf'))
bus2 = TwilightBus(["sdfsdfsfd111"])

bus1.pick("ppxia")
bus1.drop("sfs")
print(bus1.passengers)

bus2.drop("sdfsdfsfd111")
print(bus2.passengers)
http://www.pythontutor.com/vi...
儘可能別用可變類型作默認參數值, 實在要用,必須使其產生副本

del和垃圾回收

  • 有個 del 特殊方法,可是它不會銷燬實例,不該該在代碼中調用。
  • 即將銷燬實例時,Python 解釋器會調用 del 方法,給實例最後的機會,釋放外資源。
  • 本身編寫的代碼不多須要實現 del 代碼,有些 Python 新手會花時間實現,但卻吃力不討好,由於 del 很難用對。

垃圾計數器

  1. 在 CPython 中,垃圾回收使用的主要算法是引用計數。
  2. 實際上,每一個對象都會統計有多少引用指向本身。
  3. 當引用計數歸零時,對象當即就被銷燬:CPython 會在對象上調用__del__ 方法(若是定義了),而後釋放分配給對象的內存。

爲了演示對象生命結束時的情形,示例 8-16 使用 weakref.finalize 註冊一個回調函數,在銷燬對象時調用。

>>> import weakref
>>> s1 = {1, 2, 3}
>>> s2 = s1 ➊
>>> def bye(): ➋
... print('Gone with the wind...')
...
>>> ender = weakref.finalize(s1, bye) ➌
>>> ender.alive ➍
True
>>> del s1
>>> ender.alive ➎
True
>>> s2 = 'spam' ➏
Gone with the wind...
>>> ender.alive
False

❺ 如前所述,del 不刪除對象,而是刪除對象的引用。
❻ 從新綁定最後一個引用 s2,讓 {1, 2, 3} 沒法獲取。對象被銷燬了,調用了 bye 回
調,ender.alive 的值變成了 False。測試

 弱引用

  • 正是由於有引用,對象纔會在內存中存在。當對象的引用數量歸零後,垃圾回收程序會把對象銷燬。可是,有時須要引用對象,而不讓對象存在的時間超過所需時間。
  • 弱引用不會增長對象的引用數量。引用的目標對象稱爲所指對象(referent)。所以咱們說,弱引用不會妨礙所指對象被看成垃圾回收。
  • 弱引用在緩存應用中頗有用,由於咱們不想僅由於被緩存引用着而始終保存緩存對象。

弱引用是可調用的對象,返回的是被引用的對象;

>>> import weakref
>>> a_set = {0, 1}
>>> wref = weakref.ref(a_set) ➊
>>> wref
<weakref at 0x100637598; to 'set' at 0x100636748>
>>> wref() ➋
{0, 1}
>>> a_set = {2, 3, 4} ➌
>>> wref() ➍
{0, 1}
>>> wref() is None ➎
False
>>> wref() is None ➏
True

❷ 調用 wref() 返回的是被引用的對象,{0, 1}。由於這是控制檯會話,因此 {0, 1}
會綁定給 _ 變量。
❸ a_set 再也不指代 {0, 1} 集合,所以集合的引用數量減小了。可是 _ 變量仍然指代
它。
❹ 調用 wref() 依舊返回 {0, 1}。
❺ 計算這個表達式時,{0, 1} 存在,所以 wref() 不是 None。可是,隨後 _ 綁定到結
果值 False。如今 {0, 1} 沒有強引用了。
❻ 由於 {0, 1} 對象不存在了,因此 wref() 返回 None。spa

弱引用到此爲止,用到再來查 page 289

總結

  1. 變量的不是盒子,是便利貼(就是c的指針)
  2. ==是值相等 is是(內存地址相等)
  3. 默認是淺複製,就內存地址複製.深複製會有一些過深危險(能夠重寫特殊方法 __copy__() 和 __deepcopy__())
  4. 儘可能別用可變類型作默認參數值, 實在要用,必須使其產生副本
  5. 實際上,每一個對象都會統計有多少引用指向本身。 Cpython中, 當引用計數歸零時,對象當即就被銷燬:CPython會在對象上調用__del__ 方法(若是定義了),而後釋放分配給對象的內存
相關文章
相關標籤/搜索