GC機制+字元編碼+檔案操作

silence^發表於2024-04-08

深淺複製

深淺複製問題

1.1 定義理解

給一個列表,想基於這個列表進行更改生成一個新的列表。

方式一:將原來列表複製一份一摸一樣的

只對新列表生效,對原來的不生效

num_list = [1,2,3]
num_list_new = [1,2,3]
num_list_new.append(4)

print(num_list)  # [1,2,3]
print(num_list_new) # [1, 2, 3, 4]

方式二:用新的變數名指向原來的列表–>連續賦值

對原列表和新列表都生效

num_list = [1,2,3]
num_list_new = num_list
num_list_new.append(4)

print(num_list) #[1, 2, 3, 4]
print(num_list_new) # [1, 2, 3, 4]

1.2 複製原本的列表會產生的問題

新列表是複製出來的副本

【1】修改新列表會影響到原來的列表—->淺複製

【2】修改新列表不會影響到原來的列表—->深複製

1.3 在python程式碼中實現深淺複製

【1】淺複製

是修改源資料型別中的可變資料型別進行生效

copy.copy(x):返回一個與x相同的新物件,但並不是同一個物件。如果x是一個複合物件(例如列表、字典等),則只複製了物件的頂層,內部的元素仍然引用原始的物件。

import copy
num_list = [1, 2, 3, 4, [1, 2]]
num_list_new = copy.copy(num_list)
num_list_new[4].append(5)

print(num_list, id(num_list))
# [1, 2, 3, 4, [1, 2, 5]] 2279619271360
print(num_list_new, id(num_list_new))
# [1, 2, 3, 4, [1, 2, 5]] 2279619271616

【2】深複製

深複製就是將原來的列表完全複製一份,修改新列表不會影響到原來的列表

copy.deepcopy返回一個與x完全相同的新物件,並且遞迴地複製了x中的所有元素。

import copy
num_list = [1, 2, 3, 4, [1, 2]]
num_list_new = copy.deepcopy(num_list)
num_list_new[4].append(5)

print(num_list, id(num_list))
# [1, 2, 3, 4, [1, 2]] 1392648755456
print(num_list_new, id(num_list_new))
# [1, 2, 3, 4, [1, 2, 5]] 1392648755712

【總結】

  • 淺複製只會複製頂層物件,而不會影響到深層的可變資料型別

    • 淺複製複製出來的列表,其中列表中的列表引用的是原來列表中的列表,故而改變複製出來的列表,原來的也會改變。
  • 深複製會遞迴的複製整個物件的資料結構

    • 複製出來的列表,列表中的列表引用的是新的列表。

垃圾回收機制

1 什麼是垃圾回收機制

垃圾回收機制是(GC機制)python自帶的一種機制

專門用來回收變數值所佔的記憶體空間

2 堆和棧的概念

堆區:變數值存放的區域

棧區:變數名和值記憶體地址關聯的區域

【1】引用計數

變數值被變數名指向的次數

x = 10 #1
y = x  #2 --> y=x=10

【2】標記清除

當一個變數值被引用的時候,python中自帶的垃圾回收機制會定期掃描。

  • 如果這個變數值有引用—->不管
  • 如果這個變數值沒有引用—>標記

【3】分代回收

  • 新生代—>第一次被掃描,扔到新生代
  • 青春代—>直到達到新生代掃描閾值,如果還沒有指向,直接挪到青春代
  • 老年代—>直到達到青春代掃描閾值,如果還沒有指向,直接挪到老年代
  • —->直到達到老年代掃描閾值,如果還沒有指向,直接 del
# 【1】堆區
# 變數值存放於堆區,記憶體管理回收的則是堆區的內容
# # 在Python中,變數和它們所引用的物件(如數值、字串、列表、字典等)的儲存位置與記憶體管理機制密切相關。
# # 這裡提到的“變數值存放於堆區”主要涉及兩個概念:變數本身和它所引用的物件。
# # 1. 變數
#    變數是程式中用於標識資料的一個符號或名字,它並不直接儲存資料,而是指向(或引用)實際儲存資料的記憶體地址。
#    在Python直譯器內部,變數名通常儲存在棧(Stack)中。棧是一種後進先出(LIFO)的資料結構,用於高效地管理函式呼叫時的區域性變數、返回地址等資訊。
#    當建立一個變數時,Python會在當前作用域(如全域性作用域或函式區域性作用域)的棧空間內為該變數分配一個名稱,並將其關聯到相應的物件。
# # 2. 物件及值
#    Python中的物件(即變數所引用的實際資料)通常儲存在堆(Heap)中。
#    堆是一種動態分配記憶體的區域,相比於棧,其大小更為靈活且無固定上限。
#    堆用於儲存需要大量、不固定大小記憶體空間的資料結構,如大陣列、複雜的資料型別(如類例項)、以及那些生命週期可能跨越多個函式呼叫或作用域的物件。
# # 將物件值存放在堆區有以下幾個原因:
# # - 靈活性
# 		堆記憶體的大小可以動態調整,能夠適應不同大小和複雜度的物件。
#     這使得Python可以在執行時根據需要建立任意大小的物件,無需預先確定其具體尺寸,非常適合處理變長資料結構(如列表、字典)。
# # - 共享性
# 		多個變數可以同時引用同一塊堆記憶體中的物件,實現物件的共享。
#     這種機制允許透過賦值、引數傳遞、返回值等方式高效地複製“引用”,而不是複製整個物件內容,節省了記憶體資源並提高了效率。
# # - 垃圾回收
# 		由於堆記憶體中的物件可能存在多個引用,Python使用垃圾回收(Garbage Collection, GC)機制自動追蹤和管理物件的生命週期。
#     當一個物件不再有任何引用時,GC會識別並釋放其佔用的堆記憶體,防止記憶體洩漏。
#     堆區便於實施這樣的垃圾回收策略,確保程式長期執行時記憶體的有效利用。
# # 綜上所述
# 		Python將變數值(即物件)存放於堆區,主要是為了提供靈活的記憶體分配、支援物件共享以及實現自動化的垃圾回收,這些特性對於編寫高效、健壯的Python程式至關重要。
#     而變數本身作為物件的引用,則通常儲存在棧區,便於快速訪問和管理其生命週期。


# 【2】棧區
# 變數名與值記憶體地址的關聯關係通常被存放於程式的棧區

# # 1. 棧區的特性與作用
#    棧(Stack)是計算機記憶體中的一種資料結構,其主要特點是後進先出(LIFO)。在程式語言執行過程中,棧區用於儲存區域性變數、函式引數以及函式呼叫時的返回地址等臨時性資訊。
#    棧區的操作快速且高效,因為它遵循簡單的壓棧(push)和彈棧(pop)規則,適合處理那些生命週期短、使用頻繁且需要保持特定順序的資料。
# # 2. 變數生命週期管理
#    變數名與值記憶體地址的關聯關係本質上是一種符號表資訊,它記錄了原始碼中定義的變數名與其所對應的記憶體地址之間的對映。
#    這種資訊具有明顯的生命週期特徵:當進入一個作用域(如函式或程式碼塊)時,新宣告的變數及其關聯關係應被建立;
#    當離開該作用域時,這些變數及關聯關係應被自動清理以釋放資源。棧區恰好符合這種對變數生命週期進行嚴格管理的需求。
#    每當函式呼叫發生時,會在棧頂為該函式分配一塊空間,用於存放其內部宣告的變數及其關聯關係;函式呼叫結束時,這部分棧空間會被自動回收
# # 3. 效能最佳化
#    棧區訪問速度極快,因為它的記憶體地址通常連續且靠近CPU暫存器。將變數名與值記憶體地址的關聯關係放在棧上,可以確保編譯器和執行時系統能以較低的時間成本查詢並操作這些資訊。特別是在遞迴呼叫、迴圈等頻繁涉及變數訪問的場景下,棧區的高效訪問對於提升程式整體效能至關重要
# # 4. 記憶體安全性
#    棧區的記憶體由編譯器自動分配和釋放,不易出現記憶體洩漏問題。將變數名與值記憶體地址的關聯關係儲存在棧上,有助於防止因程式設計師手動管理記憶體不當而導致的錯誤。此外,棧區的大小通常有限制,這有助於防止因區域性變數過度使用記憶體而導致的堆疊溢位問題,從而提高程式穩定性
# # 5. 執行緒安全性
#    每個執行緒都有自己的棧空間,這意味著不同執行緒中的同名區域性變數各自擁有 ** 的記憶體地址和關聯關係,互不影響。這一特性保證了併發環境下的資料安全,避免了執行緒間的資料混淆。
# # 綜上所述
#     將變數名與值記憶體地址的關聯關係存放於棧區,既利用了棧區的特性實現了高效的記憶體管理、快速訪問和嚴格的生命週期控制,又確保了記憶體安全性與執行緒安全性,有利於編寫穩定、高效且易於維護的程式程式碼。
#   當然,需要注意的是,上述討論主要針對區域性變數。
#   對於靜態變數、全域性變數或動態分配的物件,它們的記憶體地址關聯關係可能存在於資料段(Data Segment)或堆區(Heap),而非棧區。

字元編碼

1. 資料存放位置

我們所學的所有資料都存放在記憶體中

如何將資料存到硬碟中就是我們要學的

2. 字元編碼介紹

計算機只能識別的只有二進位制資料,二進位制就是0/1,我們所要學習的就是將我們的文字轉換成計算機可以識別的文字。

3. 如何進行編碼

【1】解碼—>將二進位制資料轉換成字元

data = b'\xe5\xa5\x87\xe7\x89\xb9'
print(data.decode('utf-8'))
# 奇特

name = b'silence'
print(type(name))
# <class 'bytes'>

【2】編碼—>將字元轉換為二進位制資料

name = '奇特'
print(name.encode('utf-8'))
# b'\xe5\xa5\x87\xe7\x89\xb9'

將記憶體中的資料持久化儲存到檔案中

1. 開啟檔案的方式

【1】使用open語句

三種模式:a:追加寫 w:覆蓋寫 r:讀

引數是(檔案路徑,開啟檔案的模式,編輯格式)

fp = open ('01.txt', 'r', encoding='utf-8')

print(fp)
# FileNotFoundError: [Errno 2] No such file or directory: '01.text'

讀取所有檔案資料.read

fp = open ('01.txt', 'r', encoding='utf-8')

data = fp.read()
print(data) #silence

列印

print(data)
fp.close()

【2】with語句

在with語句內部開啟檔案後會自動執行close()把檔案關閉—->節省資源

with open('01.txt', 'r', encoding='utf-8') as fp:
    data = fp.read()
print(data)  # silence

2.對檔案的操作模式

【1】讀模式 r

# 01.txt檔案中存的資料是silence|741
with open('01.txt', 'r', encoding='utf-8') as fp:
	data = fp.read()
#def open(file, mode='r', buffering=None, encoding=None, errors=None, newline=None, closefd=True):  預設'r'處是隻讀
username = data.split('|')
print(username) #['silence', '741\n']

已經學過的字串格式化語法

f:格式化輸出

b:將字串轉為二進位制

r:取消\轉義

print('a\\nb')  # a\nb
print(r'a\nb')  # a\nb

【2】覆蓋寫模式 w

覆蓋寫:檔名存在則開啟,不存在則新建

開啟當前檔案並且將檔案內容清空然後寫入新內容

#(1)一次寫入
data = 'happy|123'
with open(file='01.txt', mode ='w',encoding='utf-8') as fp:
    fp.write(data)  #happy|123
    #01.txt檔案中原來存的資料是silence|741
#(2)連續寫入
data = 'happy|123'
with open(file='01.txt', mode ='w',encoding='utf-8') as fp:
    fp.write(data)  #happy|123
    fp.write('愛吃排骨') # happy|123愛吃排骨

【3】追加寫模式 a

特點: 檔案不存在則新建並存入

檔案存在則開啟並繼續寫入

不自帶換行效果 可以 +'\n'

data ='silence|741'
with open('01.txt', 'a', encoding='utf-8') as fp:
    fp.write(data)  #happy|123愛吃排骨silence|741silence|741
    # 執行幾次就寫入幾次

3.文字操作模式補充

【1】文字編輯模式

w / a/ r

完整模式應該是 wt/ at/ rt 大文字編輯模式

【2】二進位制資料模式

wb / rb

with open ('img.jpg', 'rb')as fp:
    data = fp.read()
print(data) #...\x03\x03\x89xl\xde\xeap\x88[\xb6\x19\x1b'讀取出一堆二進位制資料
with open('girl.jpg', 'wb')as fp:
    fp.write(data)
# 讀取原來照片資料  將其生成在別的

4 檔案操作擴充套件模式

r+ / w+/ a+

r:只讀 w: 只寫無法讀

with open('01.txt', 'r+', encoding='utf_8')as fp:
    fp.write('888')  # 888py|123

with open('01.txt', 'r+', encoding='utf_8')as fp:
    fp.write('888')
    # 寫完以後需要永久化儲存才能讀出來,否則是空的
    data = fp.read()
print(data)  # py|123

5 檔案的操作方法詳細

data = 'silence|741'
with open('01.txt', 'a', encoding='utf_8')as fp:
    fp.write(data + '\n')
#silence|741
#silence|741
#silence|741
#silence|741

【1】r模式的方法詳細

with open('01.txt', 'r', encoding='utf_8')as fp:
    # (1)一次性全部讀完
    data = fp.read()
print(data)

with open('01.txt', 'r', encoding='utf_8')as fp:
# (2)每次只讀一行
    data = fp.readline()
print(data)


# 迴圈單次讀出
with open('01.txt', 'r', encoding='utf_8')as fp:
    counter = 0
    while True:
        counter += 1
        data = fp.readline()
        if data:
            print(counter,data)
        else:
            break
# 1 silence|741

# 2 silence|741

# 3 silence|741

# 4 silence|741

# (3)一次性讀很多行--->把所有資料讀出來後放到一個列表
with open('01.txt', 'r', encoding='utf_8')as fp:
    data = fp.readlines()
print(data)
# ['silence|741\n', 'silence|741\n', 'silence|741\n', 'silence|741\n']

# (4)測試當前物件是否可讀
print(fp.readable())

【2】w模式的操作方法

with open('01.txt', 'w', encoding='utf_8')as fp:
    # (1)將原本內容清空並一次性寫入新內容
    fp.write('666')
    
    
data_list = ['111', '\n' , '222']
with open('01.txt', 'w', encoding='utf_8')as fp:
    fp.writelines(data_list)
# 111
# 222

7.控制檔案內的指標移動

(1).read(數字)

#檔案裡放的是123456789 
with open('01.txt', 'r', encoding='utf_8')as fp:
    # read 可以放引數,引數是讀取到哪個索引位置
    data = fp.read(4)
print(data) # 1234

(2)seek函式

f.seek(指標移動的位元組數,模式控制) 一箇中文三個位元組

0:預設模式,該模式指標是檔案開頭開始寫

1:該模式是以指標所在位置開始寫

2:該模式是在末尾開始寫

#(1) 0模式
with open('01.txt', 'r', encoding='utf_8')as fp:
    # 以開頭作為參照向後移動3個字元
    fp.seek(3,0)
    # 檢視指標所在的索引位置
    print(fp.tell()) # 3
    print(fp.read()) # 456789

#(2)1模式:以當前位置作為參照向後移動
     # 在python 3.x版本後不允許在文字檔案中使用
    #只能在二進位制中使用
with open('img.jpg', 'rb' )as fp:

    # 以當前位置作為參照向後移動字元
    fp.seek(3,1)
    print(fp.tell())
    print(fp.read())
    
#(3)2模式:以結尾位置作為參照移動(從後向前移動)
with open('img.jpg', 'rb' )as fp:

    fp.seek(-2,2)
    print(fp.tell())
    print(fp.read())  # b'\x19\x1b'
小練習
'''
張一蛋     山東    179    49    12344234523
李二蛋     河北    163    57    13913453521
王全蛋     山西    153    62    18651433422
'''

"""
張一蛋<婦女主任> 179    49    12344234523
李二蛋     河北    163    57    13913453521
王全蛋     山西    153    62    18651433422
"""
with open('01.txt', 'r+', encoding='utf-8') as sl:
    sl.seek(9, 0)
    sl.write('<婦女主任>')
    sl.seek(0, 0)
    data = sl.read()
print(data)

相關文章