2013-07-17 53 views
11

Tôi chắc chắn điều này đã được trả lời ở đâu đó nhưng tôi không chắc chắn cách mô tả nó.Tạo danh sách con bằng cách sử dụng phép nhân (*) hành vi bất ngờ

Hãy nói rằng tôi muốn tạo ra một danh sách có chứa 3 danh sách rỗng, như vậy:

lst = [[], [], []] 

Tôi nghĩ rằng tôi đã được tất cả thông minh bằng cách làm này:

lst = [[]] * 3 

Nhưng tôi phát hiện ra, sau khi gỡ lỗi một số hành vi kỳ lạ, điều này gây ra một bản cập nhật chắp thêm vào một danh sách con, nói lst[0].append(3), để cập nhật toàn bộ danh sách, làm cho nó [[3], [3], [3]] thay vì [[3], [], []].

Tuy nhiên, nếu tôi khởi tạo danh sách với

lst = [[] for i in range(3)] 

sau đó làm lst[1].append(5) cung cấp cho các dự [[], [5], []]

Câu hỏi của tôi là tại sao điều này xảy ra? Thật thú vị khi lưu ý rằng nếu tôi làm

lst = [[]]*3 
lst[0] = [5] 
lst[0].append(3) 

sau đó là 'liên kết' của tế bào bị phá vỡ 0 và tôi nhận được [[5,3],[],[]], nhưng lst[1].append(0) vẫn gây [[5,3],[0],[0].

Đoán tốt nhất của tôi là sử dụng phép nhân trong biểu mẫu [[]]*x làm cho Python lưu trữ tham chiếu đến một ô đơn lẻ ...?

+0

@RoadieRich Nhưng câu trả lời ở đây có giải thích tốt hơn và liên kết đến tài liệu chính thức. –

+1

@AseemBansal: Sau đó, những câu trả lời đó sẽ được thêm vào câu hỏi * khác * thay vì có lẽ? –

+0

Đó chắc chắn là một bản sao. http://stackoverflow.com/questions/240178/unexpected-feature-in-a-python-list-of-lists và http://stackoverflow.com/questions/1605024/python-using-the-multiply-operator- to-create-copy-of-objects-in-lists và http://stackoverflow.com/questions/6688223/python-list-multiplication-3-makes-3-lists-which-mirror-each-other-when? lq = 1 và những người khác tôi không thể tìm thấy ngay bây giờ. –

Trả lời

19

Đoán tốt nhất của tôi là sử dụng phép nhân trong biểu mẫu [[]] * x làm cho Python lưu trữ tham chiếu đến một ô đơn lẻ ...?

Có. Và bạn có thể tự mình kiểm tra điều này

>>> lst = [[]] * 3 
>>> print [id(x) for x in lst] 
[11124864, 11124864, 11124864] 

Điều này cho thấy cả ba tham chiếu đều đề cập đến cùng một đối tượng. Và lưu ý rằng nó thực sự làm cho cảm giác hoàn hảo rằng điều này xảy ra . Nó chỉ sao chép các giá trị và trong trường hợp này, các giá trị là tham chiếu. Và đó là lý do tại sao bạn thấy cùng một tham chiếu lặp đi lặp lại ba lần.

Thật thú vị khi lưu ý rằng nếu tôi làm

lst = [[]]*3 
lst[0] = [5] 
lst[0].append(3) 

sau đó là 'liên kết' của tế bào 0 là bị hỏng và tôi nhận được [[5,3],[],[]], nhưng lst[1].append(0) vẫn gây [[5,3],[0],[0].

Bạn đã thay đổi tham chiếu chiếm lst[0]; tức là, bạn đã chỉ định giá trị mới đến lst[0].Nhưng bạn không thay đổi giá trị của các yếu tố khác, chúng vẫn tham chiếu đến cùng một đối tượng mà chúng đã đề cập đến. Và lst[1]lst[2] vẫn đề cập đến chính xác cùng một phiên bản, do đó, tất nhiên, thêm một mục vào lst[1] làm cho số lst[2] cũng thấy thay đổi đó.

Đây là một sai lầm cổ điển mà mọi người mắc phải với con trỏ và tham chiếu. Đây là sự tương tự đơn giản. Bạn có một mảnh giấy. Trên đó, bạn viết địa chỉ nhà của ai đó. Bây giờ bạn lấy mảnh giấy đó, và sao chép nó hai lần để bạn kết thúc với ba mẩu giấy có cùng địa chỉ được viết trên đó. Bây giờ, hãy lấy tờ giấy đầu tiên, viết nguệch ngoạc trên địa chỉ được viết trên đó và viết một địa chỉ mới cho nhà của người khác là. Địa chỉ được viết trên hai mảnh giấy khác có thay đổi không? Tuy nhiên, đó là số chính xác là mã của bạn. Đó là lý do tại sao hai mục còn lại không thay đổi. Ngoài ra, hãy tưởng tượng chủ sở hữu của ngôi nhà có địa chỉ là vẫn còn trên mảnh giấy thứ hai sẽ tạo thêm nhà để xe cho ngôi nhà của họ. Bây giờ tôi hỏi bạn, ngôi nhà có địa chỉ trên giấy ảnh thứ ba có thêm nhà để xe không? Có, bởi vì chính nó là chính xác cùng một ngôi nhà với địa chỉ có địa chỉ được viết trên giấy thứ hai thứ hai. Điều này giải thích mọi thứ về ví dụ mã thứ hai của bạn.

: Bạn không ngờ Python sẽ gọi một "hàm tạo bản sao" phải không? Puke.

+1

+1 cho 'id()'. Điều đó sẽ hữu ích. –

+0

Cảm ơn 'id (x)', và việc xây dựng nhà để xe là một ví dụ tốt cho việc chỉnh sửa con trỏ. –

5

Điều này là do phép nhân tuần tự chỉ lặp lại các tham chiếu. Khi bạn viết [[]] * 2, bạn tạo danh sách mới có hai phần tử, nhưng cả hai yếu tố này là cùng một đối tượng trong bộ nhớ, cụ thể là danh sách trống. Do đó, một thay đổi trong một được phản ánh trong khác. Các hiểu biết, ngược lại, tạo ra một mới, độc lập danh sách trên mỗi lần lặp:

>>> l1 = [[]] * 2 
>>> l2 = [[] for _ in xrange(2)] 
>>> l1[0] is l1[1] 
True 
>>> l2[0] is l2[1] 
False 
+0

Tôi không nghĩ rằng để thử "là" nhà điều hành, cảm ơn! –

+0

@AdrianWan Không sao, tôi rất vui được giúp đỡ. – arshajii

5

Họ đang tham khảo danh sách tương tự.

Có những câu hỏi tương tự herehere

Và từ FAQ:

"* không tạo ra các bản sao, nó chỉ tạo ra tham chiếu đến đối tượng hiện có."

+0

+1 cho liên kết tới tài liệu Python. –

1

Bạn đoán rằng việc sử dụng phép nhân trong biểu mẫu [[]] * x khiến Python lưu trữ tham chiếu đến một ô là chính xác.

Vì vậy, bạn kết thúc với danh sách 3 tham chiếu đến cùng một danh sách.

1

Về cơ bản những gì đang xảy ra trong ví dụ đầu tiên của bạn là danh sách đang được tạo với nhiều tham chiếu đến cùng một danh sách bên trong. Đây là một sự cố.

>>> a = [] 
>>> b = [a] 
>>> c = b * 3 # c now contains three references to a 
>>> d = [ a for _ in xrange(4) ] # and d contains four references to a 
>>> print c 
[[], [], []] 
>>> print d 
[[], [], [], []] 
>>> a.append(3) 
>>> print c 
[[3], [3], [3]] 
>>> print d 
[[3], [3], [3], [3]] 
>>> x = [[]] * 3 # shorthand equivalent to c 
>>> print x 
[[], [], []] 
>>> x[0].append(3) 
>>> print x 
[[3], [3], [3]] 

Ở trên tương đương với ví dụ đầu tiên của bạn. Bây giờ mỗi danh sách được đưa ra biến riêng của nó, hy vọng nó rõ ràng hơn tại sao.c[0] is c[1] sẽ đánh giá là True, vì cả hai biểu thức đều đánh giá cùng một đối tượng (a).

Ví dụ thứ hai của bạn tạo nhiều đối tượng danh sách bên trong khác nhau.

>>> c = [[], [], []] # this line creates four different lists 
>>> d = [ [] for _ in xrange(3) ] # so does this line 
>>> c[0].append(4) 
>>> d[0].append(5) 
>>> print c 
[[4], [], []] 
>>> print d 
[[5], [], []]