2011-12-11 63 views
7

的有這樣的代碼:分配對象和基本類型

# assignment behaviour for integer 
a = b = 0 
print a, b # prints 0 0 
a = 4 
print a, b # prints 4 0 - different! 

# assignment behaviour for class object 
class Klasa: 
    def __init__(self, num): 
     self.num = num 

a = Klasa(2) 
b = a 
print a.num, b.num # prints 2 2 
a.num = 3 
print a.num, b.num # prints 3 3 - the same! 

問題:

  1. 爲什麼賦值運算符的工作方式不同的基本類型和 類對象(對於基本類型,它複製的價值,對於類對象,它通過引用複製)?
  2. 如何僅按值複製類對象?
  3. 如何使基準類型的引用像C++ int & b = a
+2

Python語言之外,引用/按價值的術語已經混淆和混淆了。在Python內部,其數據模型和執行模型非常特殊,這些術語仍然更加混亂和混亂,應該避免。這是我的看法,但請注意我不是Python專家。請參閱(http://stackoverflow.com/a/986145/551449)以及其他許多關於此主題的帖子和博客。看來您需要再多研究一下Python – eyquem

回答

11

這是許多Python用戶的絆腳石。對象引用語義不同於C程序員習慣的語義。

我們來看第一個案例。當您說a = b = 0時,將創建一個新的int對象,其值爲0,並創建兩個對象(一個是a,另一個是b)。這兩個變量指向同一個對象(我們創建的整數)。現在,我們運行a = 4。創建了一個新的int值對象4,並且指定了a。這意味着對4的引用次數是1,對0的引用次數已減1。

將此與C中的a = 4進行比較,其中a「指向」的內存區域被寫入。在C中爲a = b = 4意味着4被寫入兩個存儲器 - 一個用於a,另一個用於b

現在第二種情況下,a = Klass(2)創建一個Klass類型的對象,將其引用計數加1,並使a指向它。 b = a只需要指向a指向,使b指向相同的東西,並將事物的引用計數增加1。這和如果你做了a = b = Klass(2)會發生什麼一樣。嘗試打印a.numb.num與解除引用同一對象並打印屬性值相同。您可以使用內建函數id來查看對象是否相同(id(a)id(b)將返回相同的標識符)。現在,您可以通過爲其中一個屬性賦值來更改對象。由於ab指向相同的對象,因此當通過ab訪問對象時,您會發現值的更改可見。事情就是這樣。

現在,爲您的問題的答案。

  1. 賦值運算符對於這兩者的工作方式不同。它所做的只是添加對RValue的引用,並使LValue指向它。它是總是「通過引用」(儘管這個術語在參數傳遞的上下文中比簡單賦值更有意義)。
  2. 如果你想要副本的對象,請使用copy module
  3. 正如我在第1點所說的,當你做一個任務時,你總是移動參考。複製永遠不會完成,除非你要求。
+0

我認爲在「通過引用傳遞」的表達中,單詞「reference」指定一個數字==一個內存地址。但是在大多數文本中,您將「參考」一詞的含義定義爲「包含一個數字的內存位置」,也就是說,「參考」被用作同義詞「指針」。由於「指針」和「引用」是根據所考慮的語言具有浮動意義的單詞,並且Python具有特殊的數據和執行模型,因此在文本中存在令人困惑的模糊不清的氣味,就像在這個主題上的大多數文本中一樣 – eyquem

+0

你寫了''a'',你是否代表對象(內存中的位結構),對象的引用(作爲一個盒子的內存塊)還是標識符? – eyquem

+0

eyquem。我同意。在談論這個問題之前,我應該定義一些術語,因爲這是一個潛在的令人困惑的問題。 –

1

它工作不同。在您的第一個示例中,您更改了a以便ab引用不同的對象。在你的第二個例子中,你沒有,所以ab仍然引用相同的對象。

順便說一下,整數是不變的。你不能修改它們的值。你所能做的就是製作一個新的整數並重新引用你的參考。 (就像你在你的第一個例子)

5

Data Model

對象引用是Python的抽象數據。 Python 程序中的所有數據都由對象或對象之間的關係表示。 (在 感,並在符合一個的馮·諾依曼模型「存儲 程序的計算機,」代碼也由對象表示。)

從Python的角度來看,Fundamental data type是從C/C根本不同++ 。它用於將C/C++數據類型映射到Python。所以讓我們暫且不討論它,並考慮到所有數據都是對象並且是某些類的表現。每個對象都有一個ID(有點像地址),Value和一個Type。

所有對象都通過引用複製。例如

>>> x=20 
>>> y=x 
>>> id(x)==id(y) 
True 
>>> 

有一個新實例的唯一方法是創建一個。

>>> x=3 
>>> id(x)==id(y) 
False 
>>> x==y 
False 

這聽起來可能聽起來很複雜,但爲了簡化一下,Python使得某些類型不可變。例如,您不能更改string。你必須切片並創建一個新的字符串對象。

經常通過引用進行復制會給出預料不到的結果。

x=[[0]*8]*8可能給你,它創建的0個二維表的感覺。但實際上它創建相同的列表對象[0] S的參考的列表。這樣做X [1] [1]最終會在同一時間更換所有重複的實例。

Copy模塊提供了一種名爲deepcopy的方法來創建對象的新實例,而不是淺實例。當你打算有兩個不同的對象並按照你在第二個例子中的意圖單獨操作時,這是有益的。

要延長例如

>>> class Klasa: 
    def __init__(self, num): 
     self.num = num 


>>> a = Klasa(2) 
>>> b = copy.deepcopy(a) 
>>> print a.num, b.num # prints 2 2 
2 2 
>>> a.num = 3 
>>> print a.num, b.num # prints 3 3 - different! 
3 2 
+0

+1的數據和執行模型以獲取文檔的參考資料。 –

+0

@Abhijit當你寫_「通過引用拷貝」_例如''x = 20''然後''y = x'',拷貝什麼? Personnaly,我認爲沒有任何東西被複制,這就是爲什麼在某些場合,也許是所有情況下,使用「copy by」在Python中都沒有意義。 – eyquem

+0

複製用於使變量引用值的內部數據(指針,特別是C實現中的PyObject *)。 :) –

1

假設你和我有一個共同的朋友。如果我決定我不再喜歡她,她仍然是你的朋友。另一方面,如果我給她一份禮物,你的朋友收到了一份禮物。

賦值不會在Python中複製任何內容,而「通過引用複製」介於尷尬和無意義之間(正如您實際上在您的某條評論中指出的那樣)。賦值會導致變量開始引用一個值。 Python中沒有單獨的「基本類型」;而其中一些內置,int仍然是一個類。

均爲的情況下,賦值會導致變量引用右側評估的值。你所看到的行爲正是你在這種環境下應該期望的,比喻。無論您的「朋友」是int還是Klasa,分配給某個屬性與將該變量重新分配給完全的其他實例以及相應的不同行爲都有根本的不同。

唯一真正的區別是int不會碰巧有任何屬性可以分配給。 (這是實施實際上必須做一點小魔法才能限制你的部分。)

你在混淆「參考」的兩個不同概念。 C++ T&是一個神奇的東西,當它被分配時,就地更新引用的對象,而不是引用本身;一旦引用被初始化,永遠不會被「重置」。這在大多數情況下都是值的語言中很有用。在Python中,一切都是以開始的參考。 Pythonic引用更像是一個始終有效的,從不爲空的,不可用於算術的自動解引用指針。賦值使得引用開始完全引用不同的事物。你不能通過批量替換它來「更新被引用的對象」,因爲Python的對象不能像那樣工作。當然,您可以通過使用其屬性(如果有可訪問的屬性)進行更新來更新其內部狀態,但這些屬性本身也是所有引用。

相關問題