python中的值傳遞和引用傳遞(可變對象與不可變對象)也就是賦值的原理-python全部是引用傳遞


python中的值傳遞和引用傳遞(可變對象與不可變對象)也就是賦值的原理-python全部是引用傳遞

20141215 Chenxin

猜測:
1.屬於同一個類生成的對象,其默認屬性指向同一個引用.這樣當你修改一個對象的時候,會影響到其他對象,除非你通過類中的其他方法加以修改.實際上應該都是指針指向的概念.
2.基本"變量",就是不可變"對象",是調用的值傳遞.則當你重新通過"="賦值的時候,python內部是創建了一個新的值(對象)給它.(這里也就是通常所說的值傳遞).不用關心是哪種傳遞,說白了,python中都是引用傳遞.
3.對於"可變對象",如list,dict以及默認對象,完全是引用傳遞的方式.是內存中一個地址的引用,像指針一樣.
4.如果想拷貝一個對象到其他對象(副本形式),那么只能通過python的copy模塊來實現.
5.python有個緩存的概念,緩存的整數范圍為(-5,256).其他類型的不會緩存.比如x=1,y=1,x is y;x=257,y=257,x is not y; id(*)來檢查內存地址.

試驗:
關於函數參數傳遞的解釋如下(3個例子):<<python基礎教程 第二版>>P93
def ChangeInt(a):
a=10
b=2
ChangeInt(b)
print b # ->2
翻譯為非函數的樣子:
b=2
a=b #a的內存地址跟b是一樣的,說明這里是內存地址拷貝,因此賦值可以理解為指針指向
a=10 #將a指向了新創建的10對象,a的內存地址發生了改變.
print b

def ChangeList(a):
a[0]=10
b=[2]
ChangeList(b)
print b # ->[10]
翻譯為非函數:
b=[2]
a=b #a的內存地址跟b是一樣的,說明這里是內存地址拷貝,因此賦值可以理解為指針指向
a[0]=10 #將a指向的列表的第一個元素值指向內存新創建的10對象,a的內存地址沒有改變,仍然跟b的內存地址一致.這里改變的只是列表內部第一個元素的地址,並未改變a的地址(也就是a指向的列表整體的地址).
print b

def ChangeList(a):
a=[10]
b=[2]
ChangeList(b)
print b # ->[2]
翻譯為非函數:
b=[2]
a=b #a的內存地址跟b是一樣的,說明這里是內存地址拷貝,因此賦值可以理解為指針指向
a=[10] #重新創建了一個新的列表,a指向這個新列表,a的內存地址發生了改變.
print b

結論:
總結以上3個例子,可以理解為:
1.python中的賦值"="語句,可以理解為指針的指向.
2.由於指針指向問題,故才衍生出"作用域"的概念,而不是因為有作用域的"人為限制"造成的以上結果,是吧?!
3.注意區分可變對象與不可變對象的區別.list,dict,int,char,tuple...
4.以此也推出了值傳遞與引用傳遞的概念,可以統一理解為python中都是引用傳遞.

說明:
int不可變的實際情況:

list可變的實際情況:

類對象與實例對象引用傳遞的方式:

!/bin/env python

class class_a():
num_a='abc' #當字符串足夠長的時候呢?會不會是python的緩存呢?效果是一樣的,不是緩存
a=class_a
b=class_a
a.num_a='xyz' #無論字符串長短,都一樣
print a.num_a,b.num_a #輸出xyz xyz
print id(a.num_a),id(b.num_a) #輸出相同的內存地址
在這個類中,無論怎樣去改變a對象,b對象都會跟着改變.
說明該對象為可變對象.類對象只是一個初始的內存空間,之后生成實例對象會把值填充進去.???

<<完>>
知識

其實到了python中差別不大.最主要的是python是通過賦值來創建一個對象的.
所以對於python中的對象,a='1'這樣不能叫修改,只能叫創建.如果你認為這樣就是修改的話,就錯了.
一旦使用了賦值語句,就基本上與原對象無關了.#實際上是一個指針重新的指向

而且python還有可變對象和不可變對象:
象簡單類型,整數,字符串,tuple都是不可變的.
而象list, dict, object都是可變的.所以對於它們的修改一般是調用相應的方法,如對於list,你要a.append(b),對於dict你要a['b'] = '1'等.
對象本身是沒有什么變化,它的屬性或值發生變化.對於這種種情況,傳值還是引用都是一樣的.

不過,可能說引用更准確一些,比如:
def b(c):
print id(c)
a = [2]
print id(a)
b(a)
你會看到兩次打印出來的id值是一樣的.說明是引用傳遞.
也就是說函數b中的參數所指的對象就是a.
理解為傳值也無所謂,因為從賦值的角度來說,在函數中的賦值會創建新的對象,不會影響到原參數.

用指針的概念去理解更合理一些吧.

其實在python中變量名與真正的對象是一種綁定關系,或引用的關系.所以理解為引用更為合適.

和其他語言不一樣,傳遞參數的時候,python不允許程序員選擇采用傳值還是傳引用。Python參數傳遞采用的肯定是“傳對象引用”的方式。實際上,這種方式相當於傳值和傳引用的一種綜合。如果函數收到的是一個可變對象(比如字典或者列表)的引用,就能修改對象的原始值--相當於通過“傳引用”來傳遞對象。如果函數收到的是一個不可變對象(比如數字、字符或者元組)的引用,就不能直接修改原始對象--相當於通過“傳值'來傳遞對象。

python一般內部賦值變量的話,都是傳個引用變量,和C語言的傳地址的概念差不多。可以用id()來查詢內存地址.
如果a=b的話, a和b的地址是相同的;
如果只是想拷貝,那么就得用 b=a[:],這樣a和b的內存地址就不同了;

a=[1,2];b=a[:]; #這里a和b均指向一個叫做list的內存地址,list存儲的,才是具體各個值對應的內存地址.列表是雙層地址;
id(a)
140337215511368
id(b)
140337215510288 #不同

a='abc';b=a[:]; #這里是字符串形式,a指向的是'abc'的內存地址,b=a[:]后(相當於b=a),b同樣是指向'abc'的內存地址.字符串是單層地址;這里的寫法,有點多態的概念.
id(a)
140337216460600
id(b)
140337216460600 #相同

引用是指保存的值為對象的地址。在 Python 語言中,一個變量保存的值除了基本類型保存的是值外,其它都是引用,因此對於它們的使用就需要小心一些。下面舉個例子:
問題描述:已知一個列表,求生成一個新的列表,列表元素是原列表的復制
a=[1,2]
b=a
這種做法其實並未真正生成一個新的列表,b指向的仍然是a所指向的對象。這樣,如果對a或b的元素進行修改,a,b的值同時發生變化。
解決的方法為:
a=[1,2]
b=a[:]
這樣修改a對b沒有影響,修改b對a沒有影響。(列表的復制,也就是副本.不是引用傳遞)
但這種方法只適用於簡單列表,也就是列表中的元素都是基本類型,如果列表元素還存在列表的話,這種方法就不適用了。原因就是,象a[:]這種處理,只是將列表元素的值生成一個新的列表,如果列表元素也是一個列表,如:a=[1,[2]],那么這種復制對於元素[2]的處理只是復制[2]的引用,而並未生成 [2]的一個新的列表復制。
如果解決這一問題,可以使用copy模塊中的deepcopy函數。修改測試如下:
import copy
a=[1,[2]]
b=copy.deepcopy(a)
print b -->[1, [2]]
a[1].append(3)
print a -->[1, [2, 3]]
print b -->[1, [2]]
有時候知道這一點是非常重要的,因為可能你的確需要一個新的列表,並且對這個新的列表進行操作,同時不想影響原來的列表。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM