亚洲视频二区_亚洲欧洲日本天天堂在线观看_日韩一区二区在线观看_中文字幕不卡一区

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

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

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

參數(shù)傳遞

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

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

Python 參數(shù)傳遞

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

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

整型(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ù)用于返回對象的內(nèi)存地址。

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

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

以列表(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 指向相同的對象,由于列表是可變(mutable)數(shù)據(jù)類型,所以 l1.append(4)不會創(chuàng)建新的列表,仍然指向相同的對象。 由于l1 和 l2 指向相同的對象,所以列表變化也會導(dǎo)致l2的值變化。

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

Python中的'==' 和 'is'

== 和 is是Python 對象比較中常用的兩種方式,== 比較對象的值是否相等, is 比較對象的身份標(biāo)識(ID)是否相等,是否是同一個(gè)對象,是否指向同一個(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è)對象。在實(shí)際應(yīng)用中,通常使用== 來比較兩個(gè)變量的值是否相等。is 操作符常用來檢查一個(gè)變量是否為 None:

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

Python淺拷貝和深度拷貝

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

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

淺拷貝

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

下面對不可變對象整型變量和元組進(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

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

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

對可變對象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

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

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

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

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

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

深度拷貝

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

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ú)立,沒有任何聯(lián)系。

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

Python垃圾回收

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

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

引用計(jì)數(shù)是一種垃圾收集機(jī)制,當(dāng)一個(gè)python對象被引用時(shí),引用計(jì)數(shù)加 1,當(dāng)一個(gè)對象的引用為0時(shí),該對象會被當(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)去,會多一次引用。

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

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)對象被銷毀時(shí)調(diào)用。其中del a刪除了變量a,但是對象TestObjectA仍然存在,它還被b和c引用,所以不會被回收,引用計(jì)數(shù)為0時(shí)會被回收。上面的例子中,將a,b,c都刪除后引用的對象被回收(打印“666”之前)。

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

標(biāo)記清除

如果出現(xiàn)了循環(huán)引用,引用計(jì)數(shù)方法就無法回收,導(dǎo)致內(nè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,不會被回收(打印“666”之后)。

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

分代回收

如果頻繁進(jìn)行標(biāo)記清除會影響Python性能,有很多對象,清理了很多次他依然存在,可以認(rèn)為,這樣的對象不需要經(jīng)常回收,也就是說,對象存在時(shí)間越長,越可能不是垃圾。

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

gc模塊

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

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

gc 模塊函數(shù):

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

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

import gc
print(gc.get_threshold()) 

輸出:

(700, 10, 10)

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

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

Pymalloc

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

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

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

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

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

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

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

總結(jié)

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

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

分享到:
標(biāo)簽:對象 Python
用戶無頭像

網(wǎng)友整理

注冊時(shí)間:

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

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

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

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

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

答題星2018-06-03

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

全階人生考試2018-06-03

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

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

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

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

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

體育訓(xùn)練成績評定2018-06-03

通用課目體育訓(xùn)練成績評定