激情久久久_欧美视频区_成人av免费_不卡视频一二三区_欧美精品在欧美一区二区少妇_欧美一区二区三区的

腳本之家,腳本語言編程技術及教程分享平臺!
分類導航

Python|VBS|Ruby|Lua|perl|VBA|Golang|PowerShell|Erlang|autoit|Dos|bat|

服務器之家 - 腳本之家 - Python - Python對象及內存管理機制

Python對象及內存管理機制

2022-02-28 23:19測試開發小記 Python

本文主要介紹了Python的參數傳遞、淺拷貝、深拷貝,垃圾回收和內存池機制。

Python對象及內存管理機制

Python是一門面向對象的編程語言,Python中一切皆為對象,對每一個對象分配內存空間,Python的內存管理機制主要包括引用計數、垃圾回收和內存池機制。本文簡要介紹Python對象及內存管理機制。

參數傳遞

常見的參數傳遞有值傳遞和引用傳遞

  • 值傳遞就是拷貝參數的值,然后傳遞給新變量,這樣原變量和新變量之間互相獨立,互不影響。
  • 引用傳遞指把參數的引用傳給新的變量,這樣原變量和新變量指向同一塊內存地址。其中任何一個變量值改變,另外一個變量也會隨之改變。

Python 參數傳遞

Python 的參數傳遞是賦值傳遞(pass by assignment),或者叫作對象的引用傳遞(pass by object reference)。在進行參數傳遞時,新變量與原變量指向相同的對象。下面先來看一下Python中可變和不可變數據類型賦值的例子。

1. 不可變數據類型

整型(int)賦值:

a = 1 print(id(a)) b = a print(id(b)) a = a + 1 print(id(a)) c = 1 print(id(c))

執行結果:

140722100085136 140722100085136 140722100085168 140722100085136 

其中id()函數用于返回對象的內存地址。

可以看到b,c都指向了相同的對象,而a = a + 1 并不是讓 a 的值增加 1,而是重新創建并指向了新的值為 2 的對象。最終結果就是a指向了2這個新的對象,b指向1,值不變。

2. 可變數據類型

以列表(list)為例:

l1 = [1, 2, 3] print(id(l1)) # l2 = l1 print(id(l2)) l1.append(4) print(id(l1)) print(l1) print(l2)

執行結果:

1933202772296 1933202772296 1933202772296 [1, 2, 3, 4]
[1, 2, 3, 4]

l1 和 l2 指向相同的對象,由于列表是可變(mutable)數據類型,所以 l1.append(4)不會創建新的列表,仍然指向相同的對象。 由于l1 和 l2 指向相同的對象,所以列表變化也會導致l2的值變化。

可變對象(列表,字典,集合等)的改變,會影響所有指向該對象的變量。對于不可變對象(字符串、整型、元組等),所有指向該對象的變量的值總是一樣的,也不會改變。

Python中的'==' 和 'is'

== 和 is是Python 對象比較中常用的兩種方式,== 比較對象的值是否相等, is 比較對象的身份標識(ID)是否相等,是否是同一個對象,是否指向同一個內存地址。

a = 1 b = a print(id(a)) print(id(b)) print(a == b) print(a is b)

執行結果:

140722100085136 140722100085136 True True 

a和b的值相等,并指向同一個對象。在實際應用中,通常使用== 來比較兩個變量的值是否相等。is 操作符常用來檢查一個變量是否為 None:

if a is None: print("a is None") if a is not None: print("a is not None")

Python淺拷貝和深度拷貝

前面介紹了Python的賦值(對象的引用傳遞),那么Python如何解決原始數據在函數傳遞后不受影響呢,Python提供了淺度拷貝(shallow copy)和深度拷貝(deep copy)兩種方式。

  • 淺拷貝(copy):拷貝父對象,不拷貝對象內部的子對象。
  • 深拷貝(deepcopy):完全拷貝了父對象及其子對象。

淺拷貝

1. 不可變數據類型

下面對不可變對象整型變量和元組進行淺拷貝:

import copy a = 1 b = copy.copy(a) print(id(a)) print(id(b)) print(a == b) print(a is b) t1 = (1, 2, 3) t2 = tuple(t1) print(id(t1)) print(id(t2)) print(t1 == t2) print(t1 is t2)

執行結果:

50622072 50622072 True True 55145384 55145384 True True 

不可變對象的拷貝和對象的引用傳遞一樣,a、b指向相同的對象,修改其中一個變量的值不會影響另外的變量,會開辟新的空間。

2. 可變數據類型

對可變對象list進行淺拷貝:

import copy l1 = [1, 2, 3] l2 = list(l1) l3 = copy.copy(l1) l4 = l1[:] print(id(l1)) print(id(l2)) print(l1 == l2) print(l1 is l2) print(id(l3)) print(id(l4)) l1.append(4) print(id(l1)) print(l1 == l2) print(l1 is l2)

執行結果:

48520904 48523784 True False 48523848 48521032 48520904 False False 

可以看到,對可變對象的淺拷貝會重新分配一塊內存,創建一個新的對象,里面的元素是原對象中子對象的引用。改變l1的值不會影響l2,l3,l4的值,它們指向不同的對象。

上面的例子比較簡單,下面舉一個相對復雜的數據結構:

import copy l1 = [[1, 2], (4, 5)] l2 = copy.copy(l1) print(id(l1)) print(id(l2)) print(id(l1[0])) print(id(l2[0])) l1.append(6) print(l1) print(l2) l1[0].append(3) print(l1) print(l2)

執行結果:

1918057951816 1918057949448 2680328991496 2680328991496 [[1, 2], (4, 5), 6]
[[1, 2], (4, 5)]
[[1, 2, 3], (4, 5), 6]
[[1, 2, 3], (4, 5)]

l2 是 l1 的淺拷貝,它們指向不同的對象,因為淺拷貝里的元素是對原對象元素的引用,因此 l2 中的元素和 l1 指向同一個列表和元組對象(l1[0]和l2[0]指向的是相同的地址)。l1.append(6)不會對 l2 產生任何影響,因為 l2 和 l1 作為整體是兩個不同的對象,不共享內存地址。

l1[0].append(3)對 l1 中的第一個列表新增元素 3,因為 l2 是 l1 的淺拷貝,l2 中的第一個元素和 l1 中的第一個元素,共同指向同一個列表,因此 l2 中的第一個列表也會相對應的新增元素 3。

這里提一個小問題:如果對l1中的元組新增元素(l1[1] += (7, 8)),會影響l2嗎?

到這里我們知道使用淺拷貝可能帶來的副作用,要避免它就得使用深度拷貝。

深度拷貝

深度拷貝會完整地拷貝一個對象,會重新分配一塊內存,創建一個新的對象,并且將原對象中的元素以遞歸的方式,通過創建新的子對象拷貝到新對象中。因此,新對象和原對象沒有任何關聯,也就是完全拷貝了父對象及其子對象。

import copy l1 = [[1, 2], (4, 5)] l2 = copy.deepcopy(l1) print(id(l1)) print(id(l2)) l1.append(6) print(l1) print(l2) l1[0].append(3) print(l1) print(l2)

執行結果:

3026088342280 3026088342472 [[1, 2], (4, 5), 6]
[[1, 2], (4, 5)]
[[1, 2, 3], (4, 5), 6]
[[1, 2], (4, 5)]

可以看到,l1 變化不影響l2 ,l1 和 l2 完全獨立,沒有任何聯系。

在進行深度拷貝時,深度拷貝 deepcopy 中會維護一個字典,記錄已經拷貝的對象與其 ID。如果字典里已經存儲了將要拷貝的對象,則會從字典直接返回。

Python垃圾回收

Python垃圾回收包括引用計數、標記清除和分代回收

引用計數

引用計數是一種垃圾收集機制,當一個python對象被引用時,引用計數加 1,當一個對象的引用為0時,該對象會被當做垃圾回收。

from sys import getrefcount l1 = [1, 2, 3] print(getrefcount(l1)) # 查看引用計數 l2 = l1 print(getrefcount(l2))

執行結果:

2 3 

在使用 getrefcount()的時候,變量作為參數傳進去,會多一次引用。

del語句會刪除對象的一個引用。請看下面的例子

from sys import getrefcount class TestObjectA(): def __init__(self): print("hello!!!") def __del__(self): print("bye!!!") a = TestObjectA() b = a c = a print(getrefcount(c)) del a print(getrefcount(c)) del b print(getrefcount(c)) del c print("666")

執行結果:

hello!!! 4 3 2 bye!!! 666 

方法__del__ 的作用是當對象被銷毀時調用。其中del a刪除了變量a,但是對象TestObjectA仍然存在,它還被b和c引用,所以不會被回收,引用計數為0時會被回收。上面的例子中,將a,b,c都刪除后引用的對象被回收(打印“666”之前)。

另外重新賦值也會刪除對象的一個引用。

標記清除

如果出現了循環引用,引用計數方法就無法回收,導致內存泄漏。先來看下面的例子:

class TestObjectA(dict): def __init__(self): print("A: hello!!!") def __del__(self): print("A: bye!!!") class TestObjectB(dict): def __init__(self): print("B: hello!!!") def __del__(self): print("B: bye!!!") a = TestObjectA() b = TestObjectB() a['1'] = b b['1'] = a del a del b print("666")

執行結果:

A: hello!!! B: hello!!! 666 A: bye!!! B: bye!!! 

上面的代碼存在循環引用,刪除a和b之后,它們的引用計數還是1,仍然大于0,不會被回收(打印“666”之后)。

標記清除可解決循環引用問題,從根對象(寄存器和程序棧上的引用)出發,遍歷對象,將遍歷到的對象打上標記(垃圾檢測),然后在內存中清除沒有標記的對象(垃圾回收)。上面的例子中,a和b相互引用,如果與其他對象沒有引用關系就不會遍歷到它,也就不會被標記,所以會被清除。

分代回收

如果頻繁進行標記清除會影響Python性能,有很多對象,清理了很多次他依然存在,可以認為,這樣的對象不需要經常回收,也就是說,對象存在時間越長,越可能不是垃圾。

將回收對象進行分代(一共三代),每代回收的時間間隔不同,其中新創建的對象為0代,如果一個對象能在第0代的垃圾回收過程中存活下來,那么它就被放入到1代中,如果1代里的對象在第1代的垃圾回收過程中存活下來,則會進入到2代。

gc模塊

以下三種情況會啟動垃圾回收:

  • 調用gc.collect():強制對所有代執行一次回收
  • 當gc模塊的計數器達到閥值的時候。
  • 程序退出的時候

gc 模塊函數:

  • gc.enable() :啟用自動垃圾回收
  • gc.disable():停用自動垃圾回收
  • gc.isenabled():如果啟用了自動回收則返回 True。
  • gc.collect(generation=2):不設置參數會對所有代執行一次回收
  • gc.set_threshold(threshold0[, threshold1[, threshold2]]):設置垃圾回收閾值
  • gc.get_count():當前回收計數
  • 垃圾回收啟動的默認閾值
import gc print(gc.get_threshold())

輸出:

(700, 10, 10)

700是垃圾回收啟動的閾值,對象分配數量減去釋放數量的值大于 700 時,就會開始進行垃圾回收,每10次0代垃圾回收,會導致一次1代回收;而每10次1代的回收,才會有1次的2代回收。可以使用set_threshold()方法重新設置。

Python內存管理機制:Pymalloc

Pymalloc

Python實現了一個內存池(memory pool)機制,使用Pymalloc對小塊內存(小于等于256kb)進行申請和釋放管理。

當 Python 頻繁地創建和銷毀一些小的對象時,底層會多次重復調用 malloc 和 free 等函數進行內存分配。這不僅會引入較大的系統開銷,而且還可能產生大量的內存碎片。

內存池的概念就是預先在內存中申請一定數量的內存空間,當有有滿足條件的內存請求時,就先從內存池中分配內存給這個需求,如果預先申請的內存已經耗盡,Pymalloc allocator 會再申請新的內存(不能超過預先設置的內存池最大容量)。垃圾回收時,回收的內存歸還給內存池。這樣做最顯著的優勢就是能夠減少內存碎片,提升效率。

如果應用的內存需求大于 pymalloc 設置的閾值,那么解釋器再將這個請求交給底層的 C 函數(malloc/realloc/free等)來實現。

python內存池金字塔

  • 第-1層和-2層:由操作系統操作。
  • 第0層:大內存,若請求分配的內存大于256kb,使用malloc、free 等函數分配、釋放內存。
  • 第1層和第2層:由python的接口函數Pymem_Malloc實現,若請求的內存在小于等于256kb時使用該層進行分配。
  • 第3層(最上層):用戶對python對象的直接操作

Python對象及內存管理機制

圖片來源:https://www.c-sharpcorner.com/article/memory-management-in-python/

總結

本文主要介紹了Python的參數傳遞、淺拷貝、深拷貝,垃圾回收和內存池機制。

  • Python 中參數的傳遞既不是值傳遞,也不是引用傳遞,而是賦值傳遞,或者是叫對象的引用傳遞。需要注意可變對象和不可變對象的區別。比較操作符==比較對象間的值是否相等,而`is比較對象是否指向同一個內存地址。
  • 淺拷貝中的元素是對原對象中子對象的引用,如果父對象中的元素是可變的,改變它的值也會影響拷貝后的對象。深拷貝則會遞歸地拷貝原對象中的每一個子對象,是對原對象的完全拷貝。
  • Python垃圾回收包括引用計數、標記清除和分代回收三種,可以使用gc模塊來進行垃圾回收的配置。為了減少內存碎片,提升效率,Python使用了Pymalloc來管理小于等于256kb的小內存。

原文地址:https://www.toutiao.com/a7067145281871266317/

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 亚洲精品欧美二区三区中文字幕 | xxxx8| 天天草天天干天天射 | 中国杭州少妇xxxx做受 | 亚洲爱爱图 | 国产精品一区在线免费观看 | 欧美久久久一区二区三区 | 欧美一区二区黄 | 毛片视频播放 | 中文字幕综合 | 96视频在线免费观看 | 97久久日一线二线三线 | 成人福利在线视频 | 久久久久久久一区 | 欧美91看片特黄aaaa | 一级黄色av电影 | 国产一区二区视频观看 | 国产宾馆3p国语对白 | 免费a级毛片大学生免费观看 | lutube成人福利在线观看污 | 日本一区二区三区四区高清视频 | 大学生a级毛片免费视频 | 国产精品一区二区日韩 | 中文黄色一级片 | 国产久草视频在线 | 激情综合婷婷久久 | 欧美人与zoxxxx另类9 | 成人免费一区二区 | 欧美综合在线观看视频 | 亚洲第一成人在线视频 | 欧美国产综合视频 | 99久久精品免费视频 | 国产成人高清在线 | 免费播放av | 欧美日韩亚洲在线 | 禁漫天堂久久久久久久久久 | 爱逼爱操综合网 | 欧美一级视屏 | 成人福利网 | 99亚洲| 色婷婷a v |