標籤:

python列表賦值的問題?

#coding:utf-8

def fuzhi(length=4):
matrix = [[0]*length]*length
v = [1,2,3,4,5]
pprint(matrix)
print "------------before"
matrix[1][0:] = v

pprint(matrix)

def pprint(l):
for i in l:
print i

sm = fuzhi(5)

輸出

[0, 0, 0, 0, 0]
[0, 0, 0, 0, 0]
[0, 0, 0, 0, 0]
[0, 0, 0, 0, 0]
[0, 0, 0, 0, 0]
------------before
[1, 2, 3, 4, 5]
[1, 2, 3, 4, 5]
[1, 2, 3, 4, 5]
[1, 2, 3, 4, 5]
[1, 2, 3, 4, 5]

應該僅改變「矩陣」中的一行,卻改變了整個矩陣!百思不解


當你使用*創建一個列表時,[a]*n的含義是將a的值複製n份存入列表中,得到[a,a,..n個...,a]。

如果列表中是數值、字元串等基本類型沒什麼問題,當列表中存儲的是對象時就有問題了。在Python對象都是以引用的形式存在的,[[1,2,3]]*3結果其實相當[對[1,2,3]的引用,對[1,2,3]的引用,對[1,2,3]的引用]最終指向的都是一個對象,所以改一個就全改了。

上個圖:

感謝 @郭家華同學的提醒,第二張圖實質上應該直接指向對象[1,2,3],雖然和現在的圖表現出來的效果基本一致,不過內部實現應該是那樣的。


Python里的變數,其實是把 變數名 跟 對象的指針 綁定到一起。

所以這個問題,先從Python的對象說起吧,為了理解透徹,從Python的源碼開始吧,看下面這段(原來的太亂,我寫了個刪改後大致等價的):

#define PyObject_HEAD
Py_ssize_t ob_refcnt; /* 引用計數 */
struct _typeobject *ob_type; /* 當前對象的類型 */

typedef struct _object {
PyObject_HEAD
} PyObject;

typedef struct {
PyObject_HEAD
long ob_ival; //Python小整數,其實就是一個 C 語言的 long
} PyIntObject;

這樣的話,通過一個PyObject結構體的指針,就能找到這個對象的一切信息:引用計數、長度、類型對象。比如通過PyIntObject結構的ob_type指針,就能找到對Python的整數對象的類型對象,這個類型對象里,能找到做加法、減法、乘法、除法、構造、銷毀…… 等跟Python整數對象有關的所有函數。

這樣,一個Python的『對象』可以簡單的通過一個PyObject結構體的指針作為它的唯一標識。

『倆變數的對象相同』就是『一個對象的PyObject指針,與兩個變數名綁定了』。

再加個比喻,一個對象是一條狗,一個變數是牽著一條狗的繩子,多條繩子可以牽著同一條狗,你那個矩陣,就是五條繩子牽著同一條狗,你從一條繩子找到狗之後,把狗染成紅色,那麼從另外幾條繩子找到狗之後,當然會發現狗是紅的。

通過 id 函數,可以看對象的內存地址。

比如上面的代碼稍微改改:

#coding:utf-8

def fuzhi(length=4):
matrix = [[0]*length]*length
values = range(length*2)
v = [1,2,3,4,5]
pprint(matrix)
print "------------before"
matrix[1][0:] = v

pprint(matrix)

# print pointers added by @張天曉
print id(matrix[0]), id(matrix[1]), id(matrix[2])

def pprint(l):
for i in l:
print i

sm = fuzhi(5)

輸出:

[0, 0, 0, 0, 0]
[0, 0, 0, 0, 0]
[0, 0, 0, 0, 0]
[0, 0, 0, 0, 0]
[0, 0, 0, 0, 0]
------------before
[1, 2, 3, 4, 5]
[1, 2, 3, 4, 5]
[1, 2, 3, 4, 5]
[1, 2, 3, 4, 5]
[1, 2, 3, 4, 5]
4475732912 4475732912 4475732912

可見matrix[0]、matrix[1]、matrix[2] 牽著的是同一個對象。改了一個列印出來發現全都變了,那是因為列印出來的都是一個同一個對象。

你需要的,是五條狗分別綁上五條繩子,而非五條繩子綁在一隻狗身上。

下面給你一個正確的代碼,你的matrix可以像下面這樣生成:

#第一種
matrix = [[0 for i in xrange(5)] for i in xrange(5)]

#第二種
line = [0]*5
matrix = [list(line) for i in xrange(5)]

複製一個list最好並且最簡單的方法就是用內建對象list,當然你也可以用copy模塊,不過沒有直接用list來得痛快(可以參考Python Cookbook 4.1 章節 對象拷貝):

# 用內建對象list來複制
ls1 = [1, 2, 3, 4, 5]
ls2 = list(ls1)
print id(ls1), id(ls2) # 可以看到列印出了兩個不同的值

其實呢,如果你真要做矩陣運算,不如用numpy,性能好,用著爽。


首先,要分清楚Python的 可變對象不可變對象, 0 是不可變對象,[0, 0, 0, 0, 0]作為列表,是可變對象,可變對象直接乘以數字擴展相當於淺複製。(即你上面的 *length 是對同一個對象做了length次引用)

使用id就可以看到,matrix[0]~matrix[4]的結果都是一樣

#coding:utf-8

def fuzhi(length=4):
matrix = [[0]*length]*length
v = [1,2,3,4,5]
pprint(matrix)
print "------------補充:"
print id(matrix[0])
print id(matrix[1])
print id(matrix[4])
print "------------before"
matrix[1][0:] = v
pprint(matrix)
print "------------結束
"
def fuzhi2(length = 4):
matrix = [[0]*length for _ in range(length)]
v = [1,2,3,4,5]
pprint(matrix)
print "------------補充:"
print id(matrix[0])
print id(matrix[1])
print id(matrix[4])
print "------------before"
matrix[1][0:] = v
pprint(matrix)
print "------------結束
"

def pprint(l):
for i in l:
print i

sm1 = fuzhi(5)
sm2 = fuzhi2(5)

為了只改變一行,可以用Python的列表解析式,如上面代碼所示。這樣在range(length)操作中,每一次都是單獨生成一個子列表[0] * length

結果:

[0, 0, 0, 0, 0]
[0, 0, 0, 0, 0]
[0, 0, 0, 0, 0]
[0, 0, 0, 0, 0]
[0, 0, 0, 0, 0]
------------補充:
139653582793328
139653582793328
139653582793328
------------before
[1, 2, 3, 4, 5]
[1, 2, 3, 4, 5]
[1, 2, 3, 4, 5]
[1, 2, 3, 4, 5]
[1, 2, 3, 4, 5]
------------結束
[0, 0, 0, 0, 0]
[0, 0, 0, 0, 0]
[0, 0, 0, 0, 0]
[0, 0, 0, 0, 0]
[0, 0, 0, 0, 0]
------------補充:
139653582947536
139653582933024
139653582930792
------------before
[0, 0, 0, 0, 0]
[1, 2, 3, 4, 5]
[0, 0, 0, 0, 0]
[0, 0, 0, 0, 0]
[0, 0, 0, 0, 0]
------------結束


當python程序執行a = b這樣的操作的時候,會創建一個對b的新引用,列表是可變對象,引用其實還是同一個對象,可以通過下面的實例看出:

&>&>&> a = [1, 2, 3, 4]
&>&>&> b = a
&>&>&> b is a
True
&>&>&> b[2] = 1000
&>&>&> b
[1, 2, 1000, 4]
&>&>&> a
[1, 2, 1000, 4]
&>&>&>

a 和 b引用同一個對象,修改其中任意一個會影響另一個。

為了解決這種問題,可以採用複製操作:淺複製和深複製

淺複製:

淺複製將創建一個新對象,但它包含的是對原始對象中包含的項的引用。

&>&>&> a = [1, 2, [3, 4]]
&>&>&> b = list(a)
&>&>&> b is a
False
&>&>&> b.append(100)
&>&>&> b
[1, 2, [3, 4], 100]
&>&>&> a
[1, 2, [3, 4]]
&>&>&> b[2][0] = -100
&>&>&> b
[1, 2, [-100, 4], 100]
&>&>&> a
[1, 2, [-100, 4]]
&>&>&>

上面的例子可以發現,修改b中的[3, 4]對a還是有影響的。

深複製:

創建一個對象,並且遞歸地複製包含它包含的所有對象

&>&>&> import copy
&>&>&> a = [1, 2, [3, 4]]
&>&>&> b = copy.deepcopy(a)
&>&>&> b[2][0] = -100
&>&>&> b
[1, 2, [-100, 4]]
&>&>&> a
[1, 2, [3, 4]]
&>&>&>

你的代碼可以這樣修改:

#!/usr/bin/env python
# coding=utf-8
import copy

def fuzhi(length=4):
matrix = [[0] * length] * length
values = range(length * 2)
v = [1, 2, 3, 4, 5]
pprint(matrix)
print "------------before"
matrix2 = [copy.deepcopy(i) for i in matrix]
matrix2[1][0:] = v
pprint(matrix2)

def pprint(l):
for i in l:
print i

sm = fuzhi(5)


推薦閱讀:

Python離JIT還有多遠?
異常(exception)和執行失敗有什麼區別?
Python是不是弱類型?如果是的話是不是僅僅因此就不需要泛型了?
windows下anaconda 安裝報錯, errno9,怎麼解決?
spyder 如何添加和安裝其他的包?

TAG:Python |