2017-02-21 84 views
5

我是初學者,在學習python時遇到困惑。如果我有以下Python代碼:Python混淆 - 約定,名稱和值

import numpy as np 
X = np.array([1,0,0]) 
Y = X 
X[0] = 2 
print Y 

Ÿ將被證明是array([2, 0, 0])

但是,如果我做到以下幾點:

import numpy as np 
X = np.array([1,0,0]) 
Y = X 
X = 2*X 
print Y 

Y仍然是array([1,0,0])

什麼正在進行?

+1

這裏你修改*局部變量'X' *這個改變不會反映到'Y',因爲'Y'甚至不知道'X'存在。 –

+0

謝謝,對不起,我在帖子中不清楚。我在上面的原始代碼中將行X [0] = 2更改爲X = 2 * X. – SunnyIsaLearner

+1

因爲賦值給slice/indexing操作會改變數據結構*,因爲使用'*'運算符會生成* new *數據結構。另外,閱讀並理解這個:http://nedbatchelder.com/text/names.html –

回答

5

這是因爲XY引用同一對象np.array([1,0,0])這意味着不管是否一個呼叫通過XY完成,結果將是相同的,但改變一個的參考,沒有任何影響。

如果你寫:

X = np.array([1,0,0]) 
Y = X 

基本上發生的事情是,有兩個局部變量XY同一個對象。所以內存的樣子:

 +--------+ 
Y -> |np.array| <- X 
    +--------+ 
    |[1,0,0] | 
    +--------+ 

現在,如果你這樣做X[0] = 2,基本上是短的:

X.__setitem__(0,2) 

所以調用對象上的方法。所以,現在的內存看起來像:

 +--------+ 
Y -> |np.array| <- X 
    +--------+ 
    |[2,0,0] | 
    +--------+ 

不過,若你寫:

X = 2*X 

第一2*X評估。現在2*X是短期的:

X.__rmul__(2) 

(Python的首先查找是否2支持__mul__X,但由於2將引發NotImplementedException),Python將回退到X.__rmul__)。現在X.__rmul__不改變X:它留下X完好,但構造新陣列並返回。 X由現在引用該數組的新數組捕獲)。

它創建了一個新的array對象:array([4, 0, 0])然後X引用新的對象。所以,現在的內存看起來像:

 +--------+   +--------+ 
Y -> |np.array|  X ->|np.array| 
    +--------+   +--------+ 
    |[2,0,0] |   |[4,0,0] | 
    +--------+   +--------+ 

但正如你所看到的,Y仍然引用舊的對象

+0

'__setitem__'缺少一些下劃線 –

+0

@MadPhysicist:感謝您發現。 –

+0

另外,'__mul__'是一個和'__setitem__'一樣的方法,所以你不能解釋爲什麼乘法不會傳播到'Y'。 –

8

想想這樣: python中的等號表示引用。

Y = X使得ý指向相同的地址X點至

X[0] = 2使得X [0]指向2

X = 2*X使得X點到一個新的東西,但是Y爲仍然指向的地址原來的X,所以Y是不變

這是不完全正確的,但它足夠接近明白一個道理

+0

鑑於'np.ndarray'對象的工作方式,「X [0] = 2使得x [0]指向2「是完全錯誤的,但不會因爲最後一個免責聲明而倒下。 –

2

當你說X = np.array([1, 0, 0]),創建具有一些方法,幷包含實際數據和其它信息的一些內部緩存的對象。

Y = XY指同一個實際的對象。這被稱爲綁定到Python中的名稱。您也綁定了與X綁定的名稱爲Y的同一對象。

X[0] = 2調用對象的方法__setitem__,其中做一些事情的基礎緩衝區。如果修改了對象。現在,當您打印XY的值時,來自該對象緩衝區的數字爲2, 0, 0

X = 2 * X轉化爲X.__rmul__(2)。此方法不會修改X。它創建並返回一個新的數組對象,其每個元素是對應元素X的兩倍。然後,將新對象綁定到名稱X。但是,名稱Y仍然綁定到原始數​​組,因爲您沒有做任何更改。另外,因爲2.__mul__(X)不起作用,所以使用X.__rmul__。 Numpy數組自然地定義乘法是可交換的,所以X.__mul__X.__rmul__應該是一樣的。

有趣的是,您還可以執行X *= 2,這會將更改傳播到Y。這是因爲操作者*=翻譯爲__imul__方法,該方法確實修改輸入到位。

+0

感謝您指出X * = 2。我必須記住或! – SunnyIsaLearner

3

這更多的是關於公約和名稱不引用和值。

當分配:

Y = X 

然後名稱Y指對象名稱X點。在某種意義上,指針XY指向同一個對象:

X is Y # True 

is檢查名稱指向同一個對象!


然後它變得棘手:你在數組上做一些操作。

X[0] = 2 

這就是所謂的「項目任務」,並呼籲

X.__setitem__(0, 2) 

什麼__setitem__應該做的(約定)在容器X更新一些價值。因此X應該仍然指向同一個對象。

然而X * 2是「乘法」,並且約定規定這應該創建一個新對象(再次約定,您可以通過覆蓋X.__mul__來更改該行爲)。所以,當你做

X = X * 2 

名稱X現指新對象X * 2創建:

X is Y # False 

通常公共庫遵守這些約定,但強調的是,你可以完全地改變這是很重要的!

+1

我想說這是一個比約定更實際的問題。雖然約定可以規定一件事或另一件事,但numpy數組的實現最終定義了操作符的行爲。在這種情況下,按照慣例執行。 –

+0

另外,在技術上問OP問'2 * X',所以'__rmul__',但再次,對於numpy它是可交換的。 –

+0

我根據您的建議修改了我的問題標題。甚至很難爲初學者提問。謝謝! – SunnyIsaLearner