8. 深淺複製、可變與不可變、垃圾回收機制、字元編碼、檔案操作

hbutmeng發表於2024-07-23

1. 可變和不可變資料型別

1.1 概念

可變資料型別:當指定值被修改時記憶體空間地址不變

不可變資料型別:當指定值被修改時記憶體空間地址發生改變

1.2 常見型別的程式碼實現

可變型別:list dict set

不可變型別:str int float bool tuple

(1)整數 不可變

a = 1
print(id(a))  # 2549145403632
a = 2
print(id(a))  # 2549145403632

(2)浮點型 不可變

b = 1.01
print(id(b))  # 1369240625488
b = 2.02
print(id(b))  # 1369240630864

(3)字串 不可變

c = 'messi'
print(id(c))  # 2077972897840
c = 'leomessi'
print(id(c))  # 2077972974448

(4)布林值 不可變

d = True
print(d, type(d), id(d))  # True <class 'bool'> 140709452925800
d = False
print(d, type(d), id(d))  # False <class 'bool'> 140709452925832

(5)元組 不可變

e = (1, 2, 3)
print(e, type(e), id(e))  # (1, 2, 3) <class 'tuple'> 2759659910528
e = e.__add__((9, 10, 11))
print(e, type(e), id(e))  # (1, 2, 3, 9, 10, 11) <class 'tuple'> 2759660046368

(6)列表 可變

f = [1, 2, 3]
print(f, type(f), id(f))  # [1, 2, 3] <class 'list'> 2513866568320
f.append(666)
print(f, type(f), id(f))  # [1, 2, 3, 666] <class 'list'> 2513866568320

(7)字典 可變

g = {'name': 'messi'}
print(g, type(g), id(g))  # {'name': 'messi'} <class 'dict'> 2838915653376
g['name'] = 'Leomessi'
print(g, type(g), id(g))  # {'name': 'Leomessi'} <class 'dict'> 2838915653376

(8)集合 可變

h = {'messi', 'ronaldo'}
print(h, type(h), id(h))  # {'ronaldo', 'messi'} <class 'set'> 2297948368096
h.add(11)
print(h, type(h), id(h))  # {11, 'ronaldo', 'messi'} <class 'set'> 2297948368096

總結:在python中可變與不可變的區別是值被修改後記憶體空間地址是否改變

1.3 值傳遞和引用傳遞

(1)概念

值傳遞:每一次的值都會有一個自己的記憶體空間地址

引用傳遞:在中間有一塊記憶體地址負責中轉對應的值

嚴格意義上來說,python既不是值傳遞,也不是引用傳遞

(2)python的傳遞規則是:

如果傳遞的是不可變型別,在函式中修改,就不會影響原來的變數

如果傳遞的是可變資料型別,在函式中修改,不會影響原來的變數,修改,而不是重新賦值

(3)值與引用的概念

值是一個變數 = 具體的值(一塊記憶體空間放著這個變數的值)

引用是一個變數 = 記憶體地址(記憶體地址指向了值)

(4)引數傳遞

可變型別的引數傳遞:

如列表、字典,在函式中修改引數會影響原始物件

不可變型別的引數傳遞:
如數字、字串,在函式中修改引數不會影響原始物件

# 可變資料型別就是修改原來的值但是記憶體空間地址不變
# 不可變資料型別就是修改原來的值但是記憶體空間地址會改變

2. 堆和棧的概念

堆(是一種資料結構):先進先出

棧(是一種資料結構):先進後出

3. 垃圾回收機制

3.1 概念:

垃圾回收機制(簡稱GC)是Python直譯器自帶的一種機制,用來回收不可用的變數值所佔用的記憶體空間(在記憶體中,沒有變數名指向的資料都是垃圾資料)

3.2 作用:

程式執行過程中會申請大量的記憶體空間,而對於一些無用的記憶體空間如果不及時清理的話會導致記憶體使用殆盡(記憶體溢位),導致程式崩潰

3.3 堆區與棧區

在定義變數時,變數名與變數值都是需要儲存的

棧區:變數名與值記憶體地址的關聯關係通常被存放在程式的棧區

堆區:變數值存放於堆區,記憶體管理回收的是堆區的內容

3.4 原理

引用計數為主,分代回收、標記清除為輔

(1)引用計數

a = 1  # 變數值1的引用次數是1
b = a = 1  # 變數值1的引用次數是2
b = a = 2  # 變數值1的引用次數是0
a = 1  # 變數值1的引用次數是1

直譯器會將引用次數為0的資料清除

(2)標記清除

記憶體在每次執行時都會進行掃描

第一次掃描---發現某個值變成了垃圾---不會立即清除---可能在某塊程式碼又對該值進行了引用---對該垃圾打標籤---出現一次

第二次掃描---發現該值仍然是垃圾---不會立即清除---對該垃圾打標籤---出現兩次

當標籤次數為10次進行權重提升

(3)分代回收

每一次掃描都會發現垃圾 垃圾未必是當前必須清除掉的垃圾 打上標籤

等級1 : 隔 10 min 掃描一次, 一直發現有一個垃圾存在 ,當標記次數達到 100 次就提權
等級2 : 隔 20 min 掃描一次 ,一直發現有一個垃圾存在 ,當標記次數達到 200 次的時候就提權
等級3 : 隔 30 min 掃描一次, 一直發現有一個垃圾存在, 當標記次數達到 300 次的時候就清除

垃圾可能會被撿起來重新使用 為了避免重複開銷記憶體 於是就有了上述機制

4. 小整數池

python中經常使用的一些數值定義為小整數池,範圍是[-5,256],python對這些數值已經提前建立好了記憶體空間

即使多次重新定義也不會在重新建立新的空間,但是小整數池外的數值在重新定義時都會再次建立新的空間。

所以對於小整數池中的數,記憶體地址一定是相同的,小整數池外的數,記憶體地址是不同的。

5. 深淺複製

5.1 深複製

是指建立一個新的物件,該物件與原始物件完全獨立

遞迴地複製所有巢狀物件,包括內容,以便在修改新物件時不會影響到原始物件

(完整複製)

import copy
a = [1, 2, 3, [7, 8, 9]]
b = copy.deepcopy(a)  # 深複製產生的列表
print(b)  # [1, 2, 3, [7, 8, 9]]
# 修改原列表中的可變型別中的元素
a[3][0] = 666
print(a)  # 原列表[1, 2, 3, [666, 8, 9]]
print(b)  # 深複製列表[1, 2, 3, [7, 8, 9]]
# 修改原列表中的不可變型別
a[1] = 222
print(a)  # [1, 222, 3, [666, 8, 9]]
print(b)  # [1, 2, 3, [7, 8, 9]]

新物件和原物件對可變資料型別的引用不一致

5.2 淺複製

是指建立一個新的物件,並將原始物件的元素複製到新物件中

然而,如果原始物件包含可變的元素(如列表),則新物件中的這些元素仍然與原始物件中相應元素共享相同的記憶體地址

(只會複製一層)

import copy
a = [1, 2, 3]
b = copy.copy(a)  # 淺複製產生新的列表
a.append(666)
print(a)  # 原來的列表 [1, 2, 3, 666]
print(b)  # 淺複製的列表 [1, 2, 3]
b.append(999)
print(b)  # 修改淺複製的列表不會影響原列表 [1, 2, 3, 999]
import copy
c = [1, 2, 3, [7, 8, 9]]
d = copy.copy(c)  # 淺複製產生的列表
print(d)  # [1, 2, 3, [7, 8, 9]]
# 修改原列表中的可變資料型別中的元素
c[3][0] = 666
print(c)  # 原列表[1, 2, 3, [666, 8, 9]]
print(d)  # 淺複製的列表[1, 2, 3, [666, 8, 9]]
# 修改原列表中的不可變型別 c[1] = 222 print(c) # [1, 222, 3, [666, 8, 9]] print(d) # [1, 2, 3, [666, 8, 9]]

新物件和原物件中對可變資料型別的引用一致

總結:

深複製是建立一個完全獨立的物件,並且遞迴的複製物件中的所有關係

淺複製是建立一個新物件,但是新物件共享原物件中的可變資料型別關聯關係

6. 字元編碼

6.1 概念:

計算機只能識別二進位制數,人類與計算機互動時,用的都是人類能讀懂的字元,如中文字元、英文字元

人類常用的字元與計算機中的數字互動,一定經歷一個轉換翻譯過程,轉換翻譯的過程必須參照一個特定的標準,該標準稱之為字元編碼表

字元編碼表上存放的是字元與數字的一 一對應關係

編碼即指人類常用的字元轉換成計算機能夠識別的數字

6.2 字元編碼的發展歷史

第一階段(一家獨大):

計算機由美國人發明,為了讓計算機能識別英文,定義了一個英文字元與數字的對應關係---ASCII碼錶,只記錄英文字元和數字的對應關係

1Byte對應1個英文字元,1Byte=8bit,最多包含256個數字,對應著256個字元,足夠表示所有英文字元

ASCII碼錶中:

A-Z 65-90

a-z 97-122

0-9 48-57

第二階段(諸侯割據):

隨著計算機的普及和發展,各個國家都需要將本國字元與二進位制數進行轉換

GBK碼(國標):

記錄中文、英文字元與數字的對應關係

1Byte儲存英文字元,2Byte儲存中文字元,如果不夠用則使用 3Byte或 4Byte(2Byte最多能對應的中文字元數量為2**16=65536個)

Euc_kr:

記錄韓文、英文字元與數字的對應關係

shift_JIS:

記錄日文、英文字元與數字的對應關係

第三階段(天下一統):

unicode(萬國碼):

記錄了所有國家的字元與數字的對應關係,所有的字元最少採用2byte儲存

現在的計算機可以輸出所有國家的字元,記憶體使用的是unicode編碼

由於unicode最少採用2byte,而ASCII碼中1byte對應1個英文字元,因此unicode會浪費儲存空間和io時間,所以又開發了一個編碼(utf-8)

utf-8:

1byte儲存英文字元

3byte儲存中文字元

結論:

記憶體中的編碼不需要考慮,只考慮硬碟上的即可

漢字、英文------unicode------GBK

韓文、英文------unicode------Euc_k

萬國字元--------unicode-------utf-8

6.3 字元編碼應用

編碼(encode):將人類常用字元轉換成計算機能識別的二進位制數

解碼(decode):將計算機的二進位制數轉換成人類常用的字元

相關文章