在本教程中,我們將學習 Python 如何管理內存,或者 Python 如何在內部處理我們的日期。我們將深入這個主題來理解 Python 的內部工作以及它如何處理內存。
本教程將深入了解 Python 內存管理。當我們執行 Python 腳本時,Python 內存中會有很多邏輯運行,以提高代碼的效率。
介紹
內存管理對於軟體開發人員高效地使用任何編程語言非常重要。眾所周知,Python 是一種著名且廣泛使用的編程語言。它幾乎被用於每個技術領域。與編程語言相反,內存管理與編寫內存高效的代碼相關。在實現大量數據時,我們不能忽視內存管理的重要性。內存管理不當會導致應用和伺服器端組件運行緩慢。也成為工作不當的原因。如果內存處理不好,在對數據進行預處理時會花費很多時間。
在 Python 中,內存由 Python 管理器管理,它決定了應用數據在內存中的位置。所以,我們必須具備 Python 內存管理器的知識,才能寫出高效的代碼和可維護的代碼。
讓我們假設記憶看起來像一本空書,我們想在書的頁面上寫任何東西。然後,我們編寫數據管理器在書中找到空閑空間的任何數據,並將其提供給應用。向對象提供內存的過程稱為分配。
另一方面,當數據不再使用時,可以通過 Python 內存管理器將其刪除。但問題是,怎麼做?這種記憶從何而來?
Python 內存分配
對於開發人員來說,內存分配是內存管理的一個重要部分。這個過程基本上是在計算機的虛擬內存中分配空閑空間,有兩種類型的虛擬內存在執行程序時工作。
- 靜態內存分配
- 動態存儲分配
靜態內存分配-
靜態內存分配發生在編譯時。例如——在 C / C++ 中,我們聲明了一個固定大小的靜態數組。內存在編譯時分配。然而,我們不能在進一步的程序中再次使用內存。
static int a=10;
棧分配
棧數據結構用於存儲靜態內存。它只在特定的函數或方法調用中需要。每當我們調用該函數時,它都會被添加到程序的調用棧中。函數內部的變數賦值暫時存儲在函數調用棧中;函數返回該值,調用棧移動到文本任務。編譯器處理所有這些過程,所以我們不需要擔心它。
調用棧(棧數據結構)按照調用順序保存程序的操作數據,如子程序或函數調用。這些函數是在我們調用時從棧中彈出的。
動態存儲分配
與靜態內存分配不同,動態內存在運行時將內存分配給程序。例如,在 C/C++中,浮點數據類型的整數有預定義的大小,但數據類型沒有預定義的大小。內存在運行時分配給對象。我們使用 堆 來實現動態內存管理。我們可以在整個程序中使用內存。
int *a;
p = new int;
眾所周知,Python 中的一切都是對象,這意味著動態內存分配激發了 Python 的內存管理。當對象不再使用時,Python 內存管理器會自動消失。
堆內存分配
堆數據結構用於動態內存,與命名對象無關。這是一種在程序外部全局空間使用的內存類型。堆內存的一個最大優點是,如果對象不再使用或者節點被刪除,它會釋放內存空間。
在下面的例子中,我們定義了函數的變數如何存儲在棧和堆中。
默認 Python 實現
Python 是一種開源的、面向對象的編程語言,默認情況下用 C 編程語言實現。這是一個非常有趣的事實——一種用另一種語言寫的最受歡迎的語言?但這並不是一個完全的事實,而是某種程度上。
基本上,Python 語言是用英語編寫的。然而,它是在參考手冊中定義的,本身並沒有什麼用處。因此,我們需要一個基於手冊規則的解釋器。
默認實現的好處是,它在計算機中執行 Python 代碼,並且還將我們的 Python 代碼轉換為指令。因此,我們可以說 Python 的默認實現滿足了這兩個要求。
注意-虛擬機不是物理計算機,但它們是由軟體驅動的。
我們用 Python 語言編寫的程序首先轉換成與計算機相關的指令位元組碼。虛擬機解釋這個位元組碼。
Python 垃圾收集器
正如我們前面解釋的,Python 刪除了那些不再使用的對象,或者可以說它釋放了內存空間。這個消除不必要的對象內存空間的過程叫做垃圾收集器。Python 垃圾收集器啟動程序的執行,如果引用計數下降到零,它就會被激活。
當我們分配新名稱或將其放入容器(如字典或元組)中時,引用計數會增加其值。如果我們將引用重新分配給一個對象,則引用計數會減少其值。當對象的引用超出範圍或對象被刪除時,它也會降低其值。
正如我們所知,Python 使用由堆數據結構管理的動態內存分配。內存堆保存將在程序中使用的對象和其他數據結構。Python 內存管理器通過 API 函數管理堆內存空間的分配或取消分配。
內存中的 Python 對象
我們知道,Python 中的一切都是對象。對象可以是簡單的(包含數字、字元串等)。)或容器(字典、列表或用戶定義的類)。在 Python 中,在程序中使用變數或變數的類型之前,我們不需要聲明它們。
讓我們理解下面的例子。
示例-
a= 10
print(a)
del a
print(a)
輸出:
10
Traceback (most recent call last):
File "", line 1, in <module>print(x)
NameError : name 'a' is not defined</module>
正如我們在上面的輸出中所看到的,我們給對象 x 賦值並列印它。當我們移除對象 x 並嘗試在進一步的代碼中訪問時,會出現一個錯誤,聲稱變數 x 沒有被定義。
因此,Python 垃圾收集器是自動工作的,程序員不需要擔心,這一點與 c 不同
Python 中的引用計數
引用計數表示其他對象引用一個對象的次數。當對象的引用被賦值時,對象的計數增加 1。當對象的引用被移除或刪除時,對象的計數將減少。當引用計數變為零時,Python 內存管理器執行取消分配。讓我們讓它變得簡單易懂。
示例-
假設有兩個或多個變數包含相同的值,那麼 Python 虛擬機寧願在私有堆中創建另一個相同值的對象。它實際上使第二個變數指向私有堆中原來存在的值。
這非常有利於保存內存,內存可以由另一個變數使用。
x = 20
當我們給 x 賦值時,整數對象 10 在堆內存中創建,它的引用被賦給 x。
x = 20
y = x
if id(x) == id(y):
print("The variables x and y are referring to the same object")
在上面的代碼中,我們分配了 y = x,這意味著 y 對象將引用同一個對象,因為如果對象已經以相同的值存在,Python 會為新變數分配相同的對象引用。
現在,看另一個例子。
示例-
x = 20
y = x
x += 1
If id(x) == id(y):
print("x and y do not refer to the same object")
輸出:
x and y do not refer to the same object
變數 x 和 y 引用的不是同一個對象,因為 x 加 1,x 創建新的引用對象,y 仍然引用 10。
改造垃圾收集器
Python 垃圾收集器已經使用其生成對對象進行了分類。Python 垃圾收集器有三代。當我們在程序中定義新對象時,它的生命周期由垃圾收集器的第一代來處理。如果該對象在不同的程序中使用,它將被激發到下一代。每一代人都有一個門檻。
如果超過分配數減去取消分配數的閾值,垃圾收集器將開始工作。
我們可以使用 GC 模塊手動修改閾值。該模塊提供了 get_threshold() 方法來檢查不同代垃圾收集器的閾值。讓我們理解下面的例子。
示例-
Import GC
print(GC.get_threshold())
輸出:
(700, 10, 10)
在上述輸出中,閾值 700 用於第一代,其他值用於第二代和第三代。
可以使用 set_threshold() 方法修改觸發垃圾收集器的閾值。
示例- 2
import gc
gc.set_threshold(800, 20, 20)
在上面的例子中,閾值的值對於所有三代都增加了。會影響垃圾收集器的運行頻率。程序員不需要擔心垃圾收集器,但它在為目標系統優化 Python 運行時方面起著至關重要的作用。
Python 垃圾收集器為開發人員處理低級細節。
執行手動垃圾收集的重要性
正如我們前面討論的,Python 解釋器處理對程序中使用的對象的引用。當引用計數為零時,它會自動釋放內存。這是一種經典的引用計數方法,如果在程序有引用周期時無法工作。當一個或多個對象相互引用時,就會出現引用周期。因此,參考計數永遠不會變為零。
讓我們理解下面的例子-
def cycle_create():
list1 = [18, 29, 15]
list1.append(list1)
return list1
cycle_create()
[18, 29, 15, [...]]
我們已經創建了參考周期。list1 對象引用了對象 list1 本身。當函數返回對象列表 1 時,對象列表 1 的內存不會被釋放。所以參考計數不適合求解參考周期。但是,我們可以通過改變垃圾收集器或垃圾收集器的性能來解決這個問題。
為此,我們將使用 gc 模塊的 gc.collect() 函數。
import gc
n = gc.collect()
print("Number of object:", n)
上面的代碼將給出收集和取消分配的對象的數量。
我們可以使用兩種方法來執行手動垃圾收集器——基於時間或基於事件的垃圾收集。
gc.collect() 方法用於執行基於時間的垃圾收集。在固定時間間隔後調用此方法,以執行基於時間的垃圾收集。
在基於偶數的垃圾收集中, gc.collect() 函數在事件發生後調用。讓我們理解下面的例子。
示例-
import sys, gc
def cycle_create():
list1 = [18, 29, 15]
list1.append(list1)
def main():
print("Here we are creating garbage...")
for i in range(10):
cycle_create()
print("Collecting the object...")
num = gc.collect()
print("Number of unreachable objects collected by GC:", num)
print("Uncollectable garbage:", gc.garbage)
if __name__ == "__main__":
main()
sys.exit()
輸出:
Here, we are creating garbage...
Collecting the object...
Number of unreachable objects collected by GC: 10
Uncollectable garbage: []
在上面的代碼中,我們創建了由 list 變數引用的 list1 對象。列表對象的第一個元素引用自身。列表對象的引用計數總是大於零,即使它在程序中被刪除或超出範圍。
Python 內存管理
在本節中,我們將詳細討論 C Python 內存架構。
正如我們之前討論的,從物理硬體到 Python 有一個抽象層。各種應用或 Python 訪問由操作系統創建的虛擬內存。
Python 將一部分內存用於內部使用和非對象內存。內存的另一部分用於 Python 對象,如 int、dict、list 等。
CPython 包含在對象區域內分配內存的對象分配器。每當新對象需要空間時,對象分配器就會得到一個調用。分配器主要為少量數據設計,因為 Python 一次不會涉及太多數據。它在絕對需要的時候分配內存。
CPython 內存分配策略有三個主要組成部分。
Arena – 它是內存中最大的區塊,並且在內存中的頁面邊界上對齊。操作系統使用頁面邊界,即固定長度的連續內存卡盤的邊緣。Python 假設系統的頁面大小為 256 千位元組。
池- 由單個大小類組成。相同大小的池管理一個雙鏈表。池必須是已用的、已滿的或空的。一個使用的池由用於存儲數據的內存塊組成。一個完整的池擁有所有分配的和包含的數據。空池沒有任何數據,可以在需要時為數據塊分配任何大小的類。
塊- 池包含指向其「空閑」內存塊的指針。在池中,有一個指針,指示可用的內存塊。分配器直到實際需要時才接觸這些塊。
降低空間複雜度的常用方法
我們可以遵循一些最佳實踐來降低空間複雜性。這些技術被認為可以節省大量空間並使程序高效。下面是 Python 中內存分配器的一些實踐。
- 避免列表切片
我們用 Python 定義了一個列表;內存分配器分別根據列表索引分配堆的內存。假設我們需要給定列表的子列表,那麼我們執行列表切片。從原始列表中獲取子列表是一種簡單的方法。不知何故,它適用於少量數據,但不適用於大數據。
因此,列表切片生成列表中對象的副本。它只是複製了對它們的引用。結果,Python 內存分配器創建了一個對象的副本並分配它。所以我們需要避免列表切片。
避免這種情況的最好方法是開發人員嘗試使用單獨的變數來跟蹤索引,而不是對列表進行切片。
- 小心使用列表索引
開發者應盡量使用 【數組中的項目】 代替【範圍內的索引(len(數組))】以節省空間和時間。如果我們的程序不需要列表元素的索引,那麼就不要使用它。
- 字元串連接
字元串串聯不適合節省空間和時間複雜度。在可能的情況下,我們應該避免使用「+」進行字元串連接,因為字元串是不可變的。當我們將新字元串添加到現有字元串中時,Python 會創建新字元串並將其分配給新地址。
根據字元及其長度,每個字元串需要固定大小的內存。當我們更改字元串時,它需要不同的內存量,並且需要重新分配。
讓我們運行以下示例。
a = Mango
print(a)
a = a + " Ice-cream"
print (a)
輸出:
Mango
Mango Ice-cream
它將創建變數 a 來引用字元串對象,這是字元串值信息。
然後我們使用『+』運算符在其中添加新的 sting。Python 根據新字元串的大小和長度在內存中重新分配它。假設原始字元串的內存大小是 n 位元組,那麼新字元串將是 m 位元組。
我們可以使用「」來代替使用字元串串聯。join(iterable_object)」或 format 或%。這對於節省內存和時間產生了巨大的影響。
- 使用迭代器和生成器
當處理大量數據時,迭代器對時間和內存都很有幫助。處理大數據集時,我們需要立即進行數據處理,可以等待程序先處理整個數據集。
生成器是用於創建迭代器函數的特殊函數。
在下面的例子中,我們實現了一個調用特殊生成器函數的迭代器。 yield 關鍵字返回當前值,僅在循環的下一次迭代中移動到下一個值。
示例-
def __iter__(self):
''' This function allows are set to be iterable. Element can be looped over using the for loop'''
return self. _generator()
def _generator(self):
""" This function is used to implement the iterable. It stores the data we are currently on and gives the next item at each iteration of the loop."""
for i in self.items():
yield i
- 儘可能使用內置庫
如果我們使用已經在 Python 庫中預定義的方法,那麼就導入相應的庫。這會節省很多空間和時間。我們還可以創建一個模塊來定義函數,並將其導入到當前的工作程序中。
結論
在本教程中,我們討論了 Python 中內存的內部工作。我們已經學習了 Python 如何管理內存,也討論了默認的 Python 實現。CPython 是用 C 編程語言編寫的。Python 是一種動態類型的語言,它使用堆數據結構來存儲內存。
原創文章,作者:S2GWJ,如若轉載,請註明出處:https://www.506064.com/zh-tw/n/129397.html