2012-10-24 45 views
9

Tôi đã gặp phải một số sự cố khi sử dụng danh sách lồng nhau bằng Python trong mã được hiển thị.Chỉ mục danh sách lồng nhau

Về cơ bản, tôi có danh sách 2D chứa tất cả 0 giá trị, tôi muốn cập nhật giá trị danh sách trong vòng lặp.

Tuy nhiên, Python không tạo ra kết quả tôi muốn. Có điều gì mà tôi hiểu sai về chỉ số danh sách range() và Python không?

some_list = 4 * [(4 * [0])] 
for i in range(3): 
    for j in range(3): 
     some_list[i+1][j+1] = 1 
for i in range(4): 
    print(some_list[i]) 

Kết quả mong đợi của tôi là:

[0, 0, 0, 0] 
[0, 1, 1, 1] 
[0, 1, 1, 1] 
[0, 1, 1, 1] 

Nhưng kết quả thực tế từ Python là:

[0, 1, 1, 1] 
[0, 1, 1, 1] 
[0, 1, 1, 1] 
[0, 1, 1, 1] 

gì đang xảy ra ở đây?

+0

Đây là liên kết đến hướng dẫn về Python lập trình thành ngữ. Một số của nó là lỗi thời, nhưng một phần về các biến và tên vẫn tiếp tục có liên quan: http://python.net/~goodger/projects/pycon/2007/idiomatic/handout.html#other-languages-have-variables – pcurry

Trả lời

19

Vấn đề là do thực tế là python chọn chuyển danh sách xung quanh theo tham chiếu.

biến

thường được thông qua "bởi giá trị", do đó họ hoạt động độc lập:

>>> a = 1 
>>> b = a 
>>> a = 2 
>>> print b 
1 

Nhưng kể từ khi danh sách có thể nhận được khá lớn, chứ không phải là chuyển toàn bộ danh sách khoảng bộ nhớ, Python chọn để chỉ cần sử dụng một tài liệu tham khảo ('con trỏ' trong thuật ngữ C). Nếu bạn gán một biến cho một biến khác, bạn chỉ gán tham chiếu cho biến đó. Điều này có nghĩa rằng bạn có thể có hai biến trỏ đến cùng một danh sách trong bộ nhớ:

>>> a = [1] 
>>> b = a 
>>> a[0] = 2 
>>> print b 
[2] 

Vì vậy, trong dòng đầu tiên của mã bạn có 4 * [0]. Bây giờ [0] là một con trỏ đến giá trị 0 trong bộ nhớ, và khi bạn nhân nó, bạn sẽ nhận được bốn con trỏ đến cùng một vị trí trong bộ nhớ. NHƯNG khi bạn thay đổi một trong các giá trị sau đó Python biết rằng con trỏ cần phải thay đổi để trỏ đến các giá trị mới:

>>> a = 4 * [0] 
>>> a 
[0, 0, 0, 0] 
>>> [id(v) for v in a] 
[33302480, 33302480, 33302480, 33302480] 
>>> a[0] = 1 
>>> a 
[1, 0, 0, 0] 

vấn đề này được đưa ra khi bạn nhân danh sách này - bạn sẽ có được bốn bản của con trỏ danh sách. Bây giờ khi bạn thay đổi một trong các giá trị trong một danh sách, tất cả bốn sự thay đổi với nhau:

>>> a[0][0] = 1 
>>> a 
[[1, 0, 0, 0], [1, 0, 0, 0], [1, 0, 0, 0], [1, 0, 0, 0]] 

Giải pháp là để tránh những nhân thứ hai. Vòng lặp thực hiện công việc:

>>> some_list = [(4 * [0]) for _ in range(4)] 
+2

Cảm ơn bạn đã giải thích chi tiết! –

+6

Trong khi mã ở đây giải quyết vấn đề của OP, và lời giải thích xác định chính xác rằng đây là danh sách đang được truyền xung quanh 'by pointer', tôi vẫn cảm thấy bị downvote chỉ vì hai dòng đầu tiên. Các biến trong Python không phải là 'thường được truyền theo giá trị'. Mọi thứ trong Python được truyền qua 'con trỏ', nhưng các chuỗi và chuỗi (ví dụ) là bất biến nên hành vi của Python liên quan đến các kiểu đó có thể được xem là * tương đương hiệu quả * để truyền theo giá trị cho hầu hết các mục đích thực tế. –

+0

Dĩ nhiên, hầu như bạn không tăng đáng kể mức sử dụng bộ nhớ bằng cách gán cùng một chuỗi lớn cho nhiều biến hoặc chuyển nó thành các hàm như một tham số - mà bạn sẽ làm nếu chuỗi lớn được chuyển bởi giá trị. –

8

Trên thực tế tất cả các đối tượng trong danh sách của bạn là như nhau, do thay đổi một thay đổi khác quá:

In [151]: some_list = 4 * [(4 * [0])] 

In [152]: [id(x) for x in some_list] 
Out[152]: [148641452, 148641452, 148641452, 148641452] 

In [160]: some_list[0][1]=5 #you think you changed the list at index 0 here 

In [161]: some_list 
Out[161]: [[0, 5, 0, 0], [0, 5, 0, 0], [0, 5, 0, 0], [0, 5, 0, 0]] #but all lists are changed 

Tạo danh sách của bạn theo cách này:

In [156]: some_list=[[0]*4 for _ in range(4)] 

In [157]: some_list 
Out[157]: [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]] 

In [158]: [id(x) for x in some_list] 
Out[158]: [148255436, 148695180, 148258380, 148255852] 

In [163]: some_list[0][1]=5 

In [164]: some_list 
Out[164]: [[0, 5, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]] #works fine in this case 
+0

Cảm ơn bạn! Điều này thực sự hữu ích! –

+0

@KenMa vui mừng vì đã giúp. :) –

+2

Bạn thực hiện mở rộng không cần thiết thành [0 cho _ trong phạm vi (4)] - bạn chỉ có thể sử dụng 4 * [0]. Xem giải thích của tôi trong bài viết khác. –

Các vấn đề liên quan