Python函數(shù)參數(shù)傳遞機制問題在本質上是調用函數(shù)(過程)和被調用函數(shù)(過程)在調用發(fā)生時進行通信的方法問題?;镜膮?shù)傳遞
創(chuàng)新互聯(lián)是一家專注于網(wǎng)站設計制作、成都網(wǎng)站制作與策劃設計,魚峰網(wǎng)站建設哪家好?創(chuàng)新互聯(lián)做網(wǎng)站,專注于網(wǎng)站建設十余年,網(wǎng)設計領域的專業(yè)建站公司;建站業(yè)務涵蓋:魚峰等地區(qū)。魚峰做網(wǎng)站價格咨詢:13518219792
機制有兩種:值傳遞和引用傳遞。值傳遞(passl-by-value)過程中,被調函數(shù)的形式參數(shù)作為被調函數(shù)的局部變量處理,即在堆棧中開
辟了內存空間以存放由主調函數(shù)放進來的實參的值,從而成為了實參的一個副本。值傳遞的特點是被調函數(shù)對形式參數(shù)的任何操作都是作
為局部變量進行,不會影響主調函數(shù)的實參變量的值。(推薦學習:Python視頻教程)
引用傳遞(pass-by-reference)過程中,被調函數(shù)的形式參數(shù)雖然也作為局部變量在堆棧中開辟了內存空間,但是這時存放的是由主調函
數(shù)放進來的實參變量的地址。被調函數(shù)對形參的任何操作都被處理成間接尋址,即通過堆棧中存放的地址訪問主調函數(shù)中的實參變量。正
因為如此,被調函數(shù)對形參做的任何操作都影響了主調函數(shù)中的實參變量。
由于python中萬物皆對象,所以python的存儲問題是對象的存儲問題。實際上,對于每個對象,python會分配一塊內存空間去存儲它。
那么python是如何進行內存分配,如何進行內存管理,又是如何釋放內存的呢?
總結起來有一下幾個方面:引用計數(shù),垃圾回收,內存池機制
python內部使用引用計數(shù),來保持追蹤內存中的對象,Python內部記錄了對象有多少個引用,即引用計數(shù)
1、對象被創(chuàng)建 a= 'abc'
2、對象被引用 b =a
3、對象被其他的對象引用 li = [1,2,a]
4、對象被作為參數(shù)傳遞給函數(shù):foo(x)
1、變量被刪除 del a 或者 del b
2、變量引用了其他對象 b = c 或者 a = c
3、變量離開了所在的作用域(函數(shù)調用結束) 比如上面的foo(x)函數(shù)結束時,x指向的對象引用減1。
4、在其他的引用對象中被刪除(移除) li.remove(a)
5、窗口對象本身被銷毀:del li,或者窗口對象本身離開了作用域。
即對象p中的屬性引用d,而對象d中屬性同時來引用p,從而造成僅僅刪除p和d對象,也無法釋放其內存空間,因為他們依然在被引用。深入解釋就是,循環(huán)引用后,p和d被引用個數(shù)為2,刪除p和d對象后,兩者被引用個數(shù)變?yōu)?,并不是0,而python只有在檢查到一個對象的被引用個數(shù)為0時,才會自動釋放其內存,所以這里無法釋放p和d的內存空間
垃圾回收機制: ① 引用計數(shù) , ②標記清除 , ③分帶回收
引用計數(shù)也是一種垃圾收集機制, 而且也是一種最直觀, 最簡單的垃圾收集技術.當python某個對象的引用計數(shù)降為 0 時, 說明沒有任何引用指向該對象, 該對象就成為要被回收的垃圾了.(如果出現(xiàn)循環(huán)引用的話, 引用計數(shù)機制就不再起作用了)
優(yōu)點:簡單實時性,缺點:維護引用計數(shù)消耗資源,且無法解決循環(huán)引用。
如果兩個對象的引用計數(shù)都為 1 , 但是僅僅存在他們之間的循環(huán)引用,那么這兩個對象都是需要被回收的, 也就是說 它們的引用計數(shù)雖然表現(xiàn)為非 0 , 但實際上有效的引用計數(shù)為 0 ,.所以先將循環(huán)引用摘掉, 就會得出這兩個對象的有效計數(shù).
標記清除算法也有明顯的缺點:清除非活動的對象前它必須順序掃描整個堆內存,哪怕只剩下小部分活動對象也要掃描所有對象。
為了提高效率,有很多對象,清理了很多次他依然存在,可以認為,這樣的對象不需要經(jīng)?;厥眨梢园阉值讲煌募?,每個集合回收的時間間隔不同。簡單的說這就是python的分代回收。
具體來說,python中的垃圾分為1,2,3代,在1代里的對象每次回收都會去清理,當清理后有引用的對象依然存在,此時他會進入2代集合,同理2代集合清理的時候存在的對象會進入3代集合。
每個集合的清理時間如何分配:會先清理1代垃圾,當清理10次一代垃圾后會清理一次2代垃圾,當清理10次2代垃圾后會清理3代垃圾。
在Python中,許多時候申請的內存都是小塊的內存,這些小塊內存在申請后,很快又會被釋放,當創(chuàng)建大量消耗小內存的對象時,頻繁調用new/malloc會導致大量的內存碎片,致使效率降低。
內存池的概念就是預先在內存中申請一定數(shù)量的,大小相等的內存塊留作備用,當有新的內存需求時,就先從內存池中分配內存給這個需求,不夠了之后再申請新的內存。這樣做最顯著的優(yōu)勢就是能夠減少內存碎片,提升效率。
Python中有分為大內存和小內存:(256K為界限分大小內存)
大小小于256kb時,pymalloc會在內存池中申請內存空間,當大于256kb,則會直接執(zhí)行 new/malloc 的行為來申請新的內存空間
在python中 -5到256之間的數(shù)據(jù),系統(tǒng)會默認給每個數(shù)字分配一個內存區(qū)域,其后有賦值時都會指向固定的已分配的內存區(qū)域
在運行py程序的時候,解釋器會專門分配一塊空白的內存,用來存放純單詞字符組成的字符串(數(shù)字,字母,下劃線)
字符串賦值時,會先去查找要賦值的字符串是否已存在于內存區(qū)域,已存在,則指向已存在的內存,不存在,則會在大整數(shù)池中分配一塊內存存放此字符串
python的函數(shù)參數(shù)傳遞是"引用傳遞(地址傳遞)"。
python中賦值語句的過程(x = 1):先申請一段內存分配給一個整型對象來存儲數(shù)據(jù)1,然后讓變量x去指向這個對象,實際上就是指向這段內存(這里有點和C語言中的指針類似)。
在Python中,會為每個層次生成一個符號表,里層能調用外層中的變量,而外層不能調用里層中的變量,并且當外層和里層有同名變量時,外層變量會被里層變量屏蔽掉。函數(shù)? 調用 ?會為函數(shù)局部變量生成一個新的符號表。
局部變量:作用于該函數(shù)內部,一旦函數(shù)執(zhí)行完成,該變量就被回收。
全局變量:它是在函數(shù)外部定義的,作用域是整個文件。全局變量可以直接在函數(shù)里面應用,但是如果要在函數(shù)內部改變全局變量,必須使用global關鍵字進行聲明。
注意 :默認值在函數(shù)? 定義 ?作用域被解析
在定義函數(shù)時,就已經(jīng)執(zhí)行力它的局部變量
python中不可變類型是共享內存地址的:把相同的兩個不可變類型數(shù)據(jù)賦給兩個不同變量a,b,a,b在內存中的地址是一樣的。
對象vs變量
在python中,類型屬于對象,變量是沒有類型的,這正是python的語言特性,也是吸引著很多pythoner的一點。所有的變量都可以理解是內存中一個對象的“引用”,或者,也可以看似c中void*的感覺。所以,希望大家在看到一個python變量的時候,把變量和真正的內存對象分開。
類型是屬于對象的,而不是變量。
這樣,很多問題就容易思考了。
例如:
對象vs變量
12
nfoo = 1 #一個指向int數(shù)據(jù)類型的nfoo(再次提醒,nfoo沒有類型)lstFoo = [1] #一個指向list類型的lstFoo,這個list中包含一個整數(shù)1
可更改(mutable)與不可更改(immutable)對象
對應于上一個概念,就必須引出另了另一概念,這就是可更改(mutable)對象與不可更改(immutable)對象。
對于python比較熟悉的人們都應該了解這個事實,在python中,strings, tuples, 和numbers是不可更改的對象,而list,dict等則是可以修改的對象。那么,這些所謂的可改變和不可改變影響著什么呢?
可更改vs不可更改
12345
nfoo = 1nfoo = 2lstFoo = [1]lstFoo[0] = 2
代碼第2行中,內存中原始的1對象因為不能改變,于是被“拋棄”,另nfoo指向一個新的int對象,其值為2
代碼第5行中,更改list中第一個元素的值,因為list是可改變的,所以,第一個元素變更為2。其實應該說,lstFoo指向一個包含一個對象的數(shù)組。賦值所發(fā)生的事情,是有一個新int對象被指定給lstFoo所指向的數(shù)組對象的第一個元素,但是對于lstFoo本身來說,所指向的數(shù)組對象并沒有變化,只是數(shù)組對象的內容發(fā)生變化了。這個看似void*的變量所指向的對象仍舊是剛剛的那個有一個int對象的list。
如下圖所示:
Python的函數(shù)參數(shù)傳遞:傳值?引用?
對于變量(與對象相對的概念),其實,python函數(shù)參數(shù)傳遞可以理解為就是變量傳值操作,用C++的方式理解,就是對void*賦值。如果這個變量的值不變,我們看似就是引用,如果這個變量的值改變,我們看著像是在賦值。有點暈是吧,我們仍舊據(jù)個例子。
不可變對象參數(shù)調用
12345
def ChangeInt( a ): a = 10nfoo = 2 ChangeInt(nfoo)print nfoo #結果是2
這時發(fā)生了什么,有一個int對象2,和指向它的變量nfoo,當傳遞給ChangeInt的時候,按照傳值的方式,復制了變量nfoo的值,這樣,a就是nfoo指向同一個Int對象了,函數(shù)中a=10的時候,發(fā)生什么?(還記得我上面講到的那些概念么),int是不能更改的對象,于是,做了一個新的int對象,另a指向它(但是此時,被變量nfoo指向的對象,沒有發(fā)生變化),于是在外面的感覺就是函數(shù)沒有改變nfoo的值,看起來像C++中的傳值方式。
可變對象參數(shù)調用
12345
def ChangeList( a ): a[0] = 10lstFoo = [2]ChangeList(lstFoo )print nfoo #結果是[10]
當傳遞給ChangeList的時候,變量仍舊按照“傳值”的方式,復制了變量lstFoo 的值,于是a和lstFoo 指向同一個對象,但是,list是可以改變的對象,對a[0]的操作,就是對lstFoo指向的對象的內容的操作,于是,這時的a[0] = 10,就是更改了lstFoo 指向的對象的第一個元素,所以,再次輸出lstFoo 時,顯示[10],內容被改變了,看起來,像C++中的按引用傳遞。