日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網(wǎng)為廣大站長(zhǎng)提供免費(fèi)收錄網(wǎng)站服務(wù),提交前請(qǐng)做好本站友鏈:【 網(wǎng)站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(wù)(50元/站),

點(diǎn)擊這里在線(xiàn)咨詢(xún)客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會(huì)員:747

Python/ target=_blank class=infotextkey>Python是一門(mén)面向?qū)ο蟮木幊陶Z(yǔ)言,python中一切皆為對(duì)象,對(duì)每一個(gè)對(duì)象分配內(nèi)存空間,python的內(nèi)存管理機(jī)制主要包括引用計(jì)數(shù)、垃圾回收和內(nèi)存池機(jī)制。本文簡(jiǎn)要介紹python對(duì)象及內(nèi)存管理機(jī)制。

參數(shù)傳遞

常見(jiàn)的參數(shù)傳遞有值傳遞引用傳遞

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

Python 參數(shù)傳遞

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

1. 不可變數(shù)據(jù)類(lèi)型

整型(int)賦值:

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

執(zhí)行結(jié)果:

140722100085136
140722100085136
140722100085168
140722100085136

其中id()函數(shù)用于返回對(duì)象的內(nèi)存地址。

可以看到b,c都指向了相同的對(duì)象,而a = a + 1 并不是讓 a 的值增加 1,而是重新創(chuàng)建并指向了新的值為 2 的對(duì)象。最終結(jié)果就是a指向了2這個(gè)新的對(duì)象,b指向1,值不變。

2. 可變數(shù)據(jù)類(lèi)型

以列表(list)為例:

l1 = [1, 2, 3]
print(id(l1)) # 

l2 = l1
print(id(l2))

l1.Append(4)
print(id(l1))

print(l1)
print(l2)

執(zhí)行結(jié)果:

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

l1 和 l2 指向相同的對(duì)象,由于列表是可變(mutable)數(shù)據(jù)類(lèi)型,所以 l1.append(4)不會(huì)創(chuàng)建新的列表,仍然指向相同的對(duì)象。 由于l1 和 l2 指向相同的對(duì)象,所以列表變化也會(huì)導(dǎo)致l2的值變化。

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

Python中的'==' 和 'is'

== 和 is是Python 對(duì)象比較中常用的兩種方式,== 比較對(duì)象的值是否相等, is 比較對(duì)象的身份標(biāo)識(shí)(ID)是否相等,是否是同一個(gè)對(duì)象,是否指向同一個(gè)內(nèi)存地址。

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

執(zhí)行結(jié)果:

140722100085136
140722100085136
True
True

a和b的值相等,并指向同一個(gè)對(duì)象。在實(shí)際應(yīng)用中,通常使用== 來(lái)比較兩個(gè)變量的值是否相等。is 操作符常用來(lái)檢查一個(gè)變量是否為 None:

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

Python淺拷貝和深度拷貝

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

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

淺拷貝

1. 不可變數(shù)據(jù)類(lèi)型

下面對(duì)不可變對(duì)象整型變量和元組進(jìn)行淺拷貝:

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)

執(zhí)行結(jié)果:

50622072
50622072
True
True
55145384
55145384
True
True

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

2. 可變數(shù)據(jù)類(lèi)型

對(duì)可變對(duì)象list進(jìn)行淺拷貝:

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)

執(zhí)行結(jié)果:

48520904
48523784
True
False
48523848
48521032
48520904
False
False

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

上面的例子比較簡(jiǎn)單,下面舉一個(gè)相對(duì)復(fù)雜的數(shù)據(jù)結(jié)構(gòu):

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)

執(zhí)行結(jié)果:

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 的淺拷貝,它們指向不同的對(duì)象,因?yàn)闇\拷貝里的元素是對(duì)原對(duì)象元素的引用,因此 l2 中的元素和 l1 指向同一個(gè)列表和元組對(duì)象(l1[0]和l2[0]指向的是相同的地址)。l1.append(6)不會(huì)對(duì) l2 產(chǎn)生任何影響,因?yàn)?l2 和 l1 作為整體是兩個(gè)不同的對(duì)象,不共享內(nèi)存地址。

l1[0].append(3)對(duì) l1 中的第一個(gè)列表新增元素 3,因?yàn)?l2 是 l1 的淺拷貝,l2 中的第一個(gè)元素和 l1 中的第一個(gè)元素,共同指向同一個(gè)列表,因此 l2 中的第一個(gè)列表也會(huì)相對(duì)應(yīng)的新增元素 3。

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

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

深度拷貝

深度拷貝會(huì)完整地拷貝一個(gè)對(duì)象,會(huì)重新分配一塊內(nèi)存,創(chuàng)建一個(gè)新的對(duì)象,并且將原對(duì)象中的元素以遞歸的方式,通過(guò)創(chuàng)建新的子對(duì)象拷貝到新對(duì)象中。因此,新對(duì)象和原對(duì)象沒(méi)有任何關(guān)聯(lián),也就是完全拷貝了父對(duì)象及其子對(duì)象。

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)

執(zhí)行結(jié)果:

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 完全獨(dú)立,沒(méi)有任何聯(lián)系。

在進(jìn)行深度拷貝時(shí),深度拷貝 deepcopy 中會(huì)維護(hù)一個(gè)字典,記錄已經(jīng)拷貝的對(duì)象與其 ID。如果字典里已經(jīng)存儲(chǔ)了將要拷貝的對(duì)象,則會(huì)從字典直接返回。

Python垃圾回收

Python垃圾回收包括引用計(jì)數(shù)、標(biāo)記清除和分代回收

引用計(jì)數(shù)

引用計(jì)數(shù)是一種垃圾收集機(jī)制,當(dāng)一個(gè)python對(duì)象被引用時(shí),引用計(jì)數(shù)加 1,當(dāng)一個(gè)對(duì)象的引用為0時(shí),該對(duì)象會(huì)被當(dāng)做垃圾回收。

from sys import getrefcount

l1 = [1, 2, 3]
print(getrefcount(l1)) # 查看引用計(jì)數(shù)
l2 = l1
print(getrefcount(l2))

執(zhí)行結(jié)果:

2
3

在使用 getrefcount()的時(shí)候,變量作為參數(shù)傳進(jìn)去,會(huì)多一次引用。

del語(yǔ)句會(huì)刪除對(duì)象的一個(gè)引用。請(qǐng)看下面的例子

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")

執(zhí)行結(jié)果:

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

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

另外重新賦值也會(huì)刪除對(duì)象的一個(gè)引用。

標(biāo)記清除

如果出現(xiàn)了循環(huán)引用,引用計(jì)數(shù)方法就無(wú)法回收,導(dǎo)致內(nèi)存泄漏。先來(lái)看下面的例子:

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")

執(zhí)行結(jié)果:

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

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

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

分代回收

如果頻繁進(jìn)行標(biāo)記清除會(huì)影響Python性能,有很多對(duì)象,清理了很多次他依然存在,可以認(rèn)為,這樣的對(duì)象不需要經(jīng)?;厥眨簿褪钦f(shuō),對(duì)象存在時(shí)間越長(zhǎng),越可能不是垃圾。

將回收對(duì)象進(jìn)行分代(一共三代),每代回收的時(shí)間間隔不同,其中新創(chuàng)建的對(duì)象為0代,如果一個(gè)對(duì)象能在第0代的垃圾回收過(guò)程中存活下來(lái),那么它就被放入到1代中,如果1代里的對(duì)象在第1代的垃圾回收過(guò)程中存活下來(lái),則會(huì)進(jìn)入到2代。

gc模塊

以下三種情況會(huì)啟動(dòng)垃圾回收:

  1. 調(diào)用gc.collect():強(qiáng)制對(duì)所有代執(zhí)行一次回收
  2. 當(dāng)gc模塊的計(jì)數(shù)器達(dá)到閥值的時(shí)候。
  3. 程序退出的時(shí)候

gc 模塊函數(shù):

  • gc.enable() :?jiǎn)⒂米詣?dòng)垃圾回收
  • gc.disable():停用自動(dòng)垃圾回收
  • gc.isenabled():如果啟用了自動(dòng)回收則返回 True。
  • gc.collect(generation=2):不設(shè)置參數(shù)會(huì)對(duì)所有代執(zhí)行一次回收
  • gc.set_threshold(threshold0[, threshold1[, threshold2]]):設(shè)置垃圾回收閾值
  • gc.get_count():當(dāng)前回收計(jì)數(shù)

垃圾回收啟動(dòng)的默認(rèn)閾值

import gc
print(gc.get_threshold()) 

輸出:

(700, 10, 10)

700是垃圾回收啟動(dòng)的閾值,對(duì)象分配數(shù)量減去釋放數(shù)量的值大于 700 時(shí),就會(huì)開(kāi)始進(jìn)行垃圾回收,每10次0代垃圾回收,會(huì)導(dǎo)致一次1代回收;而每10次1代的回收,才會(huì)有1次的2代回收??梢允褂胹et_threshold()方法重新設(shè)置。

Python內(nèi)存管理機(jī)制:Pymalloc

Pymalloc

Python實(shí)現(xiàn)了一個(gè)內(nèi)存池(memory pool)機(jī)制,使用Pymalloc對(duì)小塊內(nèi)存(小于等于256kb)進(jìn)行申請(qǐng)和釋放管理。

當(dāng) Python 頻繁地創(chuàng)建和銷(xiāo)毀一些小的對(duì)象時(shí),底層會(huì)多次重復(fù)調(diào)用 malloc 和 free 等函數(shù)進(jìn)行內(nèi)存分配。這不僅會(huì)引入較大的系統(tǒng)開(kāi)銷(xiāo),而且還可能產(chǎn)生大量的內(nèi)存碎片。

內(nèi)存池的概念就是預(yù)先在內(nèi)存中申請(qǐng)一定數(shù)量的內(nèi)存空間,當(dāng)有有滿(mǎn)足條件的內(nèi)存請(qǐng)求時(shí),就先從內(nèi)存池中分配內(nèi)存給這個(gè)需求,如果預(yù)先申請(qǐng)的內(nèi)存已經(jīng)耗盡,Pymalloc allocator 會(huì)再申請(qǐng)新的內(nèi)存(不能超過(guò)預(yù)先設(shè)置的內(nèi)存池最大容量)。垃圾回收時(shí),回收的內(nèi)存歸還給內(nèi)存池。這樣做最顯著的優(yōu)勢(shì)就是能夠減少內(nèi)存碎片,提升效率。

如果應(yīng)用的內(nèi)存需求大于 pymalloc 設(shè)置的閾值,那么解釋器再將這個(gè)請(qǐng)求交給底層的 C 函數(shù)(malloc/realloc/free等)來(lái)實(shí)現(xiàn)。

python內(nèi)存池金字塔

  1. 第-1層和-2層:由操作系統(tǒng)操作。
  2. 第0層:大內(nèi)存,若請(qǐng)求分配的內(nèi)存大于256kb,使用malloc、free 等函數(shù)分配、釋放內(nèi)存。
  3. 第1層和第2層:由python的接口函數(shù)Pymem_Malloc實(shí)現(xiàn),若請(qǐng)求的內(nèi)存在小于等于256kb時(shí)使用該層進(jìn)行分配。
  4. 第3層(最上層):用戶(hù)對(duì)python對(duì)象的直接操作
Python對(duì)象及內(nèi)存管理機(jī)制

圖片來(lái)源:https://www.c-sharpcorner.com/article/memory-management-in-python/

總結(jié)

本文主要介紹了Python的參數(shù)傳遞、淺拷貝、深拷貝,垃圾回收和內(nèi)存池機(jī)制。

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

分享到:
標(biāo)簽:對(duì)象 Python
用戶(hù)無(wú)頭像

網(wǎng)友整理

注冊(cè)時(shí)間:

網(wǎng)站:5 個(gè)   小程序:0 個(gè)  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會(huì)員

趕快注冊(cè)賬號(hào),推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨(dú)大挑戰(zhàn)2018-06-03

數(shù)獨(dú)一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過(guò)答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫(kù),初中,高中,大學(xué)四六

運(yùn)動(dòng)步數(shù)有氧達(dá)人2018-06-03

記錄運(yùn)動(dòng)步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績(jī)?cè)u(píng)定2018-06-03

通用課目體育訓(xùn)練成績(jī)?cè)u(píng)定