python3[進階]8.對象引用、可變性和垃圾回收

文章目錄

    • 8.1變量不是盒子
    • 8.2 標識,相等性和別名
      • 8.2.1 在==和is之間選擇
      • 8.2.2 元組的相對不可變性
    • 8.3 默認做淺復制
      • (拓展)為任意對象做深復制和淺復制
      • 深拷貝和淺拷貝有什么具體的區別呢?
    • 8.4 函數的參數作為引用時
      • 8.4.1 不要使用可變類型作為參數的默認值
    • 總結(閱讀)
      • 8.4.2 防御可變參數
    • 8.5 del和垃圾回收
    • 8.6 弱引用

8.1變量不是盒子

python變量類似于Java中的引用型變量,因此最好把他們理解為附注在對象上的標注.

a = [1,2,3]
b = a
a.append(7)
print(b)

輸出為:

[1, 2, 3, 7]
// 可以發現,a和b引用同一個列表,而不是那個列表的副本

在這里插入圖片描述

因為變量只不過是標注,所以可以為對象貼上多個標注,貼的多個標注就是別名.

8.2 標識,相等性和別名

每個變量都有標識,類型和值.對象一旦創建,它的標識一定不會變;可以把標識(ID)理解為對象在內存中的地址.
is運算符比較兩個對象的標識;
id()函數返回對象標識的整數表示.

標識最常使用is運算符檢查,而不是直接比較ID.

charles = {'name':'charles', 'born':'1832'}
lewis = charles
print(lewis is charles)
print(id(charles), id(lewis))
lewis['balence'] = 950
print(charles)
alex = {'name': 'charles', 'born': '1832', 'balence': 950}
print(alex == charles)
print(alex is charles)

輸出:

True
140640659352168 140640659352168
{'name': 'charles', 'born': '1832', 'balence': 950}
True //比較兩個對象,結果相同,這是因為dic類的__eq__方法就是這樣實現的
False //但是他們是不同的對象,標識不同.
//可以發現,charles和lewis綁定同一個對象,alex綁定另外一個對象

在這里插入圖片描述

8.2.1 在==和is之間選擇

==運算符比較兩個對象的值(對象中保存的數據),而is比較對象的標識(標識就是在內存中的位置)
在變量和"單例值"之間比較時,應該使用is.可以使用is檢查變量綁定的值是不是None.

 x is None

is 運算符比 == 速度快,因為它不能重載,所以 Python 不用尋找并調用特殊方法,而是直接比較兩個整數 ID。而 a == b 是語法糖,等同于 a.eq(b)。繼承自 object 的__eq__ 方法比較兩個對象的 ID,結果與 is 一樣。但是多數內置類型使用更有意義的方式覆蓋了 eq 方法,會考慮對象屬性的值。相等性測試可能涉及大量處理工作。

8.2.2 元組的相對不可變性

元組與多數 Python 集合(列表、字典、集,等等)一樣,保存的是對象的引用。而 str、bytes 和 array.array 等單一類型序列是扁平的,它們保存的不是引用,而是在連續的內存中保存數據本身(字符、字節和數字)。
元組的不可變性其實是指tuple數據結構的物理內容(保存的引用)不可變,與引用的對象無關.
復制對象時,相等性和一致性之間的區別有更深入的影響。副本與源對象相等,但是ID不同。可是,如果對象中包含其他對象,那么應該復制內部對象嗎?可以共享內部對象嗎?這些問題沒有唯一的答案。

8.3 默認做淺復制

l1 = [3, [55, 44], (7, 8, 9)]
l2 = list(l1)
print(l2)
print(l2 == l1)
print(l2 is l1)
print(l2[2] is l1[2])
l3 =l1[:]
print(l3)
print(l3 == l1)
print(l3 is l1)
print(l3[2] is l1[2])
l4 = l1
print(l4 == l1)
print(l4 is l1)
l1[1].append(66)
print(l1)
print(l2)
print(l3)
print(l4)

輸出結果如下:
在這里插入圖片描述
上圖發現:

  • l2和l3對應著關于l1的淺拷貝,l4直接將l1起了一個別名,也就是說l4和l1指向了同一個對象。
  • 淺拷貝對于內層引用有影響,即內層引用還是指向了同一個對象。
  • [3, [55, 44], (7, 8, 9)] //list(l1)創建l1的副本
    True //副本和源列表相等

對于列表和其他可變序列來說,還可以使用更簡潔的l3 = l1[:]語句來創建副本
然而,構造方法或者[:] 做的是淺復制(就是復制了最外層容器,副本中的元素是源容器中元素的引用).如果所有的元素都是不可變的,那么這樣沒有問題,如果有可變的元素,會出現問題.

 l1 = [3,[66,55,44],(7,8,9)]
l2 = list(l1)
l1.append(100)
l1[1].remove(55)
print('l1:',l1)
print('l2:',l2)
l2[1]+=[33,22]
l2[2]+=(10,11)
print('l1:',l1)
print('l2:',l2)

輸出:

l1: [3, [66, 44], (7, 8, 9), 100]
l2: [3, [66, 44], (7, 8, 9)]
l1: [3, [66, 44, 33, 22], (7, 8, 9), 100]
l2: [3, [66, 44, 33, 22], (7, 8, 9, 10, 11)] 
# 對**元組**來說,+=運算符創建一個新元組,然后重新綁定給變量l2[2].現在l1和l2中最后位置上的元組不是同一個對象.
# 總結:對+=和×=所做的增量賦值來說,如果左邊的變量綁定的是不可變對象,會創建新對象;如果是可變對象,會就地修改。

如圖:
在這里插入圖片描述

(拓展)為任意對象做深復制和淺復制

淺復制沒什么問題,但有時我們需要的是深復制(即副本不共享內部對象的引用)。

import copy
class Bus:def __init__(self, passengers=None):if passengers is None:self.passengers = []else:self.passengers = passengersdef 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)

輸出:

140694379943920 140694379943976 140694379944088
['Alice', 'Claire', 'David']
140694377362824 140694377362824 140694377336776
['Alice', 'Bill', 'Claire', 'David']

結果如圖:
在這里插入圖片描述
使用 copy 和 deepcopy,創建 3 個不同的 Bus 實例。
審查 passengers 屬性后發現:

  • bus1 和 bus2 共享同一個列表對象,因為 bus2 是bus1 的淺復制副本。
  • bus3 是 bus1 的深復制副本,因此它的 passengers 屬性指代另一個列表。
    從上面可以發現,深拷貝和淺拷貝都會創建不同的對象,深拷貝是完全拷貝一個新的對象,淺拷貝不會拷貝子對象。

深拷貝和淺拷貝有什么具體的區別呢?

import copy
a = [1,2,3,['a','b','c']]
b = copy.copy(a)
c = copy.deepcopy(a)
a[3].append('d')
print(a)
print(b)
print(c)

結果如圖:
在這里插入圖片描述
從上圖我們可以發現,

  • copy.deepcopy()會完全拷貝一個新的對象出現;
  • copy.copy()不會拷貝其子對象,也就是說,如果原來的對象里面又包含別的對象的引用,則這個新的對象還是會指向這個舊的內層引用。
    總結:
    copy.copy() 淺復制,不會拷貝其子對象,修改子對象,將受影響 .
    copy.deepcopy() 深復制,將拷貝其子對象,修改子對象,將不受影響.

8.4 函數的參數作為引用時

python唯一支持的參數傳遞模式是共享傳參(call by sharing).
共享傳參指函數的各個形式參數獲得實參中各個引用的副本,也就是說,函數內部的形參是實參的別名。
這種方案的結果是,函數可能會修改作為參數傳入的可變對象,但是無法修改那些對象的標識(即不能把一個對象替換成另一個對象)。

def f(a,b):a += breturn a
x,y = 1,2
print(f(x, y))
print(x, y)
a = [1, 2]
b = [3, 4]
print(f(a, b))
print(a, b)
t = (10, 20)
u = (30, 40)
print(f(t, u))
print(t, u)

輸出:

3
1 2  
[1, 2, 3, 4]
[1, 2, 3, 4] [3, 4]
(10, 20, 30, 40)
(10, 20) (30, 40)

我們發現數字x沒變,列表a變了,元組t沒變

8.4.1 不要使用可變類型作為參數的默認值

可選參數可以有默認值,這是python函數定義的一個很好的特性.但是我們應該避免使用可變的對象作為參數的默認值.

class HauntedBus:"""備受幽靈乘客折磨的校車"""def __init__(self, passengers=[]):self.passengers = passengersdef pick(self,name):self.passengers.append(name)def drop(self,name):self.passengers.remove(name)bus1 = HauntedBus(['Alice','Bill'])
print(bus1.passengers)
bus1.pick('Charlie')
bus1.drop('Alice')
print(bus1.passengers)
bus2 = HauntedBus()
bus2.pick('Carrie')
print(bus2.passengers)
bus3 = HauntedBus()
print(bus3.passengers)
bus3.pick('Dive')
print(bus2.passengers)
print(bus2.passengers is bus3.passengers)
print(bus1.passengers)

輸出:

['Alice', 'Bill']
['Bill', 'Charlie']
['Carrie']
['Carrie']//bus3一開始是空的,但是默認列表卻不為空
['Carrie', 'Dive']
True
['Bill', 'Charlie']

問題在于,沒有指定初始乘客的HauntedBus實例會共享同一個乘客列表。
使用可變類型作為函數參數的默認值有危險,因為如果就地修改了參數,默認值也就變了,這樣會影響以后使用默認值的調用。
修正的方法很簡單:在__init__方法中,傳入passengers參數時,應該把參數值的副本賦值給self.passengers,

def __init__(self,passengers =None):if passengers is None:self.passengers = []else:self.passengers = list(passengers)

總結(閱讀)

  1. 關于+和extend()方法,+是創建了新對象,extend是就地連接。
  2. 淺復制分為兩類:
    • 第一類:t2 = t1[:]或者 t2 = list(t1) 都是了新的對象t2.但是內層引用還是指向同一個對象。
    • 第二類:l2 = copy.copy(l1)
  3. 深復制: l3 = copy.deepcopy(l1) 完全拷貝了一個新的對象。
  4. 關于增量運算符+=和×=,以+=為例:a+=b,若+=前面的a為可變序列(例如list),則就地解決,若a為不可變序列(例如tuple),則會創建新的對象。
  5. 實例中圖片經過:http://www.pythontutor.com/ 生成

8.4.2 防御可變參數

如果定義的函數接收可變參數,應該謹慎考慮調用方是否期望修改傳入的參數。
示例 8-15 一個簡單的類,說明接受可變參數的風險

class TwilightBus:"""讓乘客銷聲匿跡的校車"""def __init__(self, passengers=None):if passengers is None:self.passengers = []else:self.passengers = passengers def pick(self, name):self.passengers.append(name)def drop(self, name):self.passengers.remove(name)

測試一下

>>> basketball_team = ['Sue', 'Tina', 'Maya', 'Diana', 'Pat'] 
>>> bus = TwilightBus(basketball_team) 
>>> bus.drop('Tina') 
>>> bus.drop('Pat')
>>> basketball_team 
['Sue', 'Maya', 'Diana']

發現:下車的學生從籃球隊中消失了!
TwilightBus 違反了設計接口的最佳實踐,即“最少驚訝原則”。學生從校車中下車后,她的名字就從籃球隊的名單中消失了,這確實讓人驚訝。

這里的問題是,校車為傳給構造方法的列表創建了別名。正確的做法是,校車自己維護乘客列表。修正的方法很簡單:在 init 中,傳入 passengers 參數時,應該把參數值的副本賦值給 self.passengers,像示例 8-8 中那樣做(8.3 節)。

def __init__(self, passengers=None):
if passengers is None:self.passengers = []
else:self.passengers = list(passengers) ?

? 創建 passengers 列表的副本;如果不是列表,就把它轉換成列表。在內部像這樣處理乘客列表,就不會影響初始化校車時傳入的參數了。此外,這種處理方式還更靈活:現在,傳給 passengers 參數的值可以是元組或任何其他可迭代對象,例如set 對象,甚至數據庫查詢結果,因為 list 構造方法接受任何可迭代對象。

8.5 del和垃圾回收

del 語句刪除名稱,而不是對象。del 命令可能會導致對象被當作垃圾回收,但是僅當刪除的變量保存的是對象的最后一個引用,或者無法得到對象時。 重新綁定也可能會導致對象的引用數量歸零,導致對象被銷毀。

在 CPython 中,垃圾回收使用的主要算法是引用計數。實際上,每個對象都會統計有多少引用指向自己。當引用計數歸零時,對象立即就被銷毀:CPython 會在對象上調用__del__ 方法(如果定義了),然后釋放分配給對象的內存。
CPython 2.0 增加了分代垃圾回收算法,用于檢測引用循環中涉及的對象組——如果一組對象之間全是相互引用,即
使再出色的引用方式也會導致組中的對象不可獲取。Python 的其他實現有更復雜的垃圾回收程序,而且不依賴引用計數,這意味著,對象的引用數量為零時可能不會立即調用__del__ 方法。

8.6 弱引用

正是因為有引用,對象才會在內存中存在。當對象的引用數量歸零后,垃圾回收程序會把對象銷毀。但是,有時需要引用對象,而不讓對象存在的時間超過所需時間。這經常用在緩存中。
弱引用不會增加對象的引用數量。引用的目標對象稱為所指對象(referent)。因此我們說,弱引用不會妨礙所指對象被當作垃圾回收。
弱引用在緩存應用中很有用,因為我們不想僅因為被緩存引用著而始終保存緩存對象。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/273853.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/273853.shtml
英文地址,請注明出處:http://en.pswp.cn/news/273853.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

openfire修改服務器名稱方法

1.登陸openfire管理頁面,在主頁面下方選擇編輯屬性,修改服務器名稱為當前主機名稱,點擊保存屬性,按頁面提示重啟服務器。 2.重啟后,主頁的服務器屬性下的服務器名稱出現一個嘆號,鼠標放上去顯示Found RSA c…

python (第八章)補充-可迭代對象(補充高階函數,以及常用的高階函數)

文章目錄可迭代對象迭代器什么是迭代器什么是生成器生成器的作用生成器的注意事項總結:高階函數什么是高階函數?map()函數filter()函數reduce()函數參考可迭代對象 我們已經知道,可以直接作用于for循環的數據類型有以下幾種: 一類…

網絡閱讀開篇

網絡閱讀也符合馬太效應,投入的時間越多,獲取的有效信息卻越來越少,因此做出以下規定: 1、限制網絡閱讀時間; 2、每次閱讀做總結。 本來想的挺簡單的,隨便搜了一下,居然一部小心拜讀了兩位大神的…

python (第二章)數據結構

文章目錄2.5 對序列使用 和 建立由列表組成的列表2.6序列的增量賦值(和)關于 的謎題補充:extend()方法和有什么區別呢?2.7 list.sort方法和內置函數sorted(排序)2.8 用bisect來管理已排序的序列2.8.2用bisect.insort插入元素2.9 當…

數據庫 CURD測試題【簡單】

文章目錄1.組合兩個表基本信息要求答案2.第二高的薪水基本信息要求答案3.查找重復的電子郵箱基本信息要求答案4.超過經理收入的員工基本信息要求答案:5.超過5名學生的課信息:要求答案6.有趣的電影信息要求答案7.交換工資(updeta,條件判斷&…

JAVA學習資料整理

今天偶然間發現之前一個群里發過的一篇關于JAVA學習資料的東西。本著服務大眾的精神,搬來了博客園: 《JAVA編程思想》第四版(英文原版)下載地址:http://115.com/file/e7fzi0fm《JAVA開發實戰經典》下載地址&#xff1a…

mysql快速了解

文章目錄數據庫了解:快速操作:安裝mysql啟動,關閉,重啟mysql服務連接mysql的root用戶創建數據庫刪除數據庫選擇數據庫mysql 數據類型MySQL 創建數據表MySQL 刪除數據表MySQL 插入數據MySQL 查詢數據MySQL WHERE 子句BINARY 關鍵字MySQL UPDATE 更新批量更…

javascript編程風格(粗略筆記)

1、空格 緊湊型&#xff1a;    project.MyClass function(arg1, arg2){  松散型&#xff1a;    for( i 0; i < length; i ){ 2、代碼行長度  最多80個字符 3、命名: 采用駝峰式方法命名(開始的第一個單詞小寫&#xff0c;之后的所有單詞首字母大寫)  var …

數據結構 面試題

文章目錄1.數組1.1 尋找數組中第二小的元素1.2 找到數組中第一個不重復出現的整數1.3合并兩個有序數組1.4 重新排列數組中的正值和負值2.棧2.1 前綴表達式&#xff0c;中綴表達式&#xff0c;后綴表達式2.1.1 中綴表達式轉化為后綴表達式2.1.2 中綴表達式轉化為前綴表達式2.2使…

WPF之無法觸發KeyDown或者KeyUp鍵盤事件

有時候我們可能在Panel(StackPanel、Canvas、Grid)上或者是在一些默認不支持Focus的控件上添加了KeyDown或者KeyUp&#xff0c;可是殘酷的現實告訴我們&#xff0c;這是無法觸發的&#xff0c;怎么辦呢&#xff0c;很簡單&#xff0c;只需一句代碼。 private void MouseLeftBut…

宣布在日本地區正式發布 Windows Azure

&#xfeff;&#xfeff;昨天&#xff0c;我與 Microsoft 日本的集團副總裁 Yasuyuki Higuchi 一同站在臺上&#xff0c;宣布在兩個新地區正式發布 Windows Azure&#xff1a;日本東部和日本西部。能夠親自見證 Microsoft 對日本市場的持續承諾&#xff0c;對我來說是莫大的榮…

運行cmd狀態下MySQL導入導出.sql文件

MySQL導入導出.sql文件步驟如下&#xff1a; 一.MySQL的命令行模式的設置&#xff1a; 桌面->我的電腦->屬性->環境變量->新建-> PATH“&#xff1b;path\mysql\bin;”其中path為MySQL的安裝路徑。 二.簡單的介紹一下命令行進入MySQL的方法&#xff1a; 1.C:\&g…

python中的collections

文章目錄deque(雙向列表)defaultdict(為不存在的key設置默認值)OrderedDictOrderedDict可以實現一個FIFO&#xff08;先進先出&#xff09;的dict&#xff0c;Counter參考collections是Python內建的一個集合模塊&#xff0c;提供了許多有用的集合類。deque(雙向列表) 使用list…

mysql 面試點

文章目錄mysql 運算符1.mysql運算符中的 !和Not的區別&#xff1f;CRUD1.條件判斷的用法2. not exits 和not in 的區別3. case when語句mysql函數1.to_days()連接1.什么時候選擇右連接&#xff0c;什么時候選擇左連接&#xff1f;mysql 運算符 1.mysql運算符中的 !和Not的區別…

[Windows Phone] 實作不同的地圖顯示模式

[Windows Phone] 實作不同的地圖顯示模式 原文:[Windows Phone] 實作不同的地圖顯示模式前言 本文章主要示范如何讓地圖有不同的模式產生&#xff0c;例如平面圖、地形圖、鳥瞰圖、鳥瞰圖含街道等。 這部分主要是調整 Map.CartographicMode 屬性&#xff0c;其中 MapCartograph…

數據庫 CURD測試題【中等】

文章目錄1.換座位&#xff08;交換相鄰的id&#xff09;基本信息要求答案[case when]2.分數排名(分組&#xff0c;排序)基本信息要求答案3.連續出現的數字(連接)信息要求答案4.第N高的薪水(函數)信息要求答案5.各個部門工資最高的員工(分組&#xff0c;連接)信息要求答案6.統計…

[STemWin教程入門篇]第一期:emWin介紹

特別說明&#xff1a;原創教程&#xff0c;未經許可禁止轉載&#xff0c;教程采用回復可見的形式&#xff0c;謝謝大家的支持。 armfly-x2,x3,v2,v3,v5開發板裸機和帶系統的emWin工程已經全部建立&#xff0c;鏈接如下&#xff1a; http://bbs.armfly.com/read.php?tid1830 SE…

python 棧【測試題】

文章目錄1.刪除最外層的括號信息要求答案2.棒球比賽信息示例答案3. 用棧實現隊列要求說明:答案4.用隊列模擬棧描述注意答案5.下一個更大的元素&#xff08;未解&#xff09;信息&#xff1a;示例&#xff1a;注意:答案&#xff1a;6.刪除字符串中的所有相鄰重復項信息示例&…

python從socket做個websocket的聊天室server

下面的是server端&#xff1a;把IP改成自己的局域網IP&#xff1a; #coding:utf8 import socket,select import SocketServer import hashlib,base64,time from pprint import pprint#author:lijim def f(key):skey"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"sha1hashli…