Tại sao tôi không thể sử dụng danh sách làm khóa dict trong python?


100

Tôi hơi bối rối về những gì có thể / không thể được sử dụng làm khóa cho một lệnh python.

dicked = {}
dicked[None] = 'foo'     # None ok
dicked[(1,3)] = 'baz'    # tuple ok
import sys
dicked[sys] = 'bar'      # wow, even a module is ok !
dicked[(1,[3])] = 'qux'  # oops, not allowed

Vì vậy, một tuple là một loại bất biến nhưng nếu tôi ẩn danh sách bên trong nó, thì nó không thể là khóa .. tôi không thể dễ dàng ẩn danh sách bên trong một mô-đun?

Tôi đã có một số ý tưởng mơ hồ rằng khóa phải "có thể băm" nhưng tôi sẽ thừa nhận sự thiếu hiểu biết của mình về các chi tiết kỹ thuật; Tôi không biết điều gì đang thực sự xảy ra ở đây. Điều gì sẽ xảy ra nếu bạn cố gắng sử dụng danh sách làm khóa, với băm như vị trí bộ nhớ của chúng?


1
Đây là một cuộc thảo luận hay: stackoverflow.com/questions/2671211/…
Hernan,

49
Đã cười khúc khích với tên biến của bạn.
kindall

Câu trả lời:


33

Có một bài viết hay về chủ đề trong Python wiki: Tại sao danh sách không thể trở thành khóa từ điển . Như đã giải thích ở đó:

Điều gì sẽ xảy ra nếu bạn cố gắng sử dụng danh sách làm khóa, với băm như vị trí bộ nhớ của chúng?

Nó có thể được thực hiện mà không thực sự vi phạm bất kỳ yêu cầu nào, nhưng nó dẫn đến hành vi không mong muốn. Các danh sách thường được xử lý như thể giá trị của chúng được lấy từ giá trị nội dung của chúng, chẳng hạn như khi kiểm tra bình đẳng (in-). Nhiều người - có thể hiểu được - mong đợi rằng bạn có thể sử dụng bất kỳ danh sách nào [1, 2]để lấy cùng một khóa, nơi bạn phải giữ chính xác cùng một đối tượng danh sách. Nhưng tra cứu theo giá trị sẽ ngắt ngay sau khi danh sách được sử dụng làm khóa được sửa đổi và để tra cứu theo danh tính yêu cầu bạn giữ chính xác cùng một danh sách - điều này không bắt buộc đối với bất kỳ thao tác danh sách phổ biến nào khác (ít nhất là tôi không thể nghĩ đến ).

Các đối tượng khác như mô-đun và objectdù sao cũng tạo ra một phần lớn hơn nhiều so với danh tính đối tượng của chúng (lần cuối cùng bạn có hai đối tượng mô-đun riêng biệt được gọi là khi sysnào?), Và vẫn được so sánh bằng cách đó. Do đó, ít ngạc nhiên hơn - hoặc thậm chí được mong đợi - rằng chúng, khi được sử dụng làm khóa dict, cũng so sánh theo danh tính trong trường hợp đó.


30

Tại sao tôi không thể sử dụng danh sách làm khóa dict trong python?

>>> d = {repr([1,2,3]): 'value'}
{'[1, 2, 3]': 'value'}

(cho bất kỳ ai vấp phải câu hỏi này đang tìm cách giải quyết nó)

như những người khác giải thích ở đây, thực sự là bạn không thể. Tuy nhiên, bạn có thể sử dụng biểu diễn chuỗi của nó thay thế nếu bạn thực sự muốn sử dụng danh sách của mình.


5
Xin lỗi, tôi không thực sự hiểu ý của bạn. Nó không khác gì việc sử dụng các ký tự chuỗi làm các phím.
wim

11
Thật; Tôi vừa thấy rất nhiều câu trả lời thực sự giải thích lý do tại sao bạn không thể sử dụng danh sách theo nghĩa 'khóa phải có thể băm', điều này đúng đến mức tôi muốn đề xuất một cách giải quyết vấn đề này, đề phòng trường hợp ai đó (mới) sẽ tìm kiếm nó ...
Remi

5
Tại sao không chỉ chuyển đổi danh sách thành một tuple? Tại sao chuyển đổi nó thành một chuỗi? Nếu bạn sử dụng bộ tuple, nó sẽ hoạt động chính xác với các lớp có phương pháp so sánh tùy chỉnh __eq__. Nhưng nếu bạn chuyển đổi chúng thành chuỗi, mọi thứ được so sánh bằng cách biểu diễn chuỗi của nó.
Aran-Fey

điểm tốt @ Aran-Fey. Chỉ cần đảm bảo rằng bất kỳ phần tử nào trong tuple đều có thể được băm. ví dụ: tuple ([[1,2], [2,3]]) làm khóa sẽ không hoạt động vì các phần tử của tuple vẫn là danh sách.
Remi

17

Chỉ cần tìm thấy, bạn có thể thay đổi Danh sách thành bộ, sau đó sử dụng nó làm khóa.

d = {tuple([1,2,3]): 'value'}

15

Vấn đề là các bộ giá trị là bất biến và danh sách thì không. Hãy xem xét những điều sau

d = {}
li = [1,2,3]
d[li] = 5
li.append(4)

Những gì nên d[li]trả lại? Có phải là cùng một danh sách? Làm thế nào về d[[1,2,3]]? Nó có các giá trị giống nhau, nhưng là một danh sách khác?

Cuối cùng, không có câu trả lời thỏa đáng. Ví dụ: nếu khóa duy nhất hoạt động là khóa gốc, thì nếu bạn không có tham chiếu đến khóa đó, bạn không bao giờ có thể truy cập lại giá trị. Với mọi khóa được phép khác, bạn có thể tạo khóa mà không cần tham chiếu đến khóa gốc.

Nếu cả hai đề xuất của tôi đều hoạt động, thì bạn có các khóa rất khác nhau trả về cùng một giá trị, điều này gây ngạc nhiên hơn một chút. Nếu chỉ nội dung ban đầu hoạt động, thì khóa của bạn sẽ nhanh chóng bị hỏng, vì danh sách được thực hiện để sửa đổi.


Có, đó là cùng một danh sách nên tôi mong đợi d[li]vẫn là 5. d[[1,2,3]]sẽ tham chiếu đến một đối tượng danh sách khác làm khóa, vì vậy nó sẽ là KeyError. Tôi thực sự không thấy bất kỳ vấn đề nào .. ngoại trừ việc để một khóa được thu thập rác có thể khiến một số giá trị dict không thể truy cập được. Nhưng đó là một vấn đề thực tế không phải là một vấn đề logic ..
wim

@wim: d[list(li)]là KeyError là một phần của vấn đề. Trong hầu hết các trường hợp sử dụng khác , lisẽ không thể phân biệt được với một danh sách mới có nội dung giống hệt nhau. Nó hoạt động, nhưng nó phản trực quan đối với nhiều người. Thêm vào đó, lần cuối cùng bạn thực sự phải sử dụng danh sách làm khóa dict là khi nào? Trường hợp sử dụng duy nhất mà tôi có thể tưởng tượng là khi bạn băm mọi thứ theo danh tính, và trong trường hợp đó, bạn chỉ nên làm điều đó thay vì dựa vào __hash__và dựa trên __eq__danh tính.

@delnan Có phải vấn đề chỉ đơn giản là nó sẽ không hữu ích lắm vì những biến chứng như vậy không? hoặc có một số lý do tại sao nó thực sự có thể phá vỡ một chính tả?
wim

1
@wim: Cái sau. Như đã nêu trong câu trả lời của tôi, nó không thực sự phá vỡ các yêu cầu đối với phím dict, nhưng nó có khả năng gây ra nhiều vấn đề hơn là nó giải quyết được.

1
@delnan - bạn định nói 'người cũ'
Jason

9

Đây là câu trả lời http://wiki.python.org/moin/DictionaryKeys

Điều gì sẽ xảy ra nếu bạn cố gắng sử dụng danh sách làm khóa, với băm như vị trí bộ nhớ của chúng?

Việc tra cứu các danh sách khác nhau có cùng nội dung sẽ tạo ra các kết quả khác nhau, mặc dù so sánh các danh sách có cùng nội dung sẽ chỉ ra chúng là tương đương.

Điều gì về Sử dụng một danh sách theo nghĩa đen trong tra cứu từ điển?


3

Bạn có thể tìm thấy chiếc bạt của bạn ở đây:

Tại sao danh sách không thể là khóa từ điển

Những người mới làm quen với Python thường thắc mắc tại sao, trong khi ngôn ngữ này bao gồm cả kiểu tuple và kiểu danh sách, các bộ giá trị có thể được sử dụng như một khóa từ điển, trong khi danh sách thì không. Đây là một quyết định thiết kế có chủ ý và tốt nhất có thể được giải thích bằng cách hiểu rõ cách thức hoạt động của từ điển Python.

Nguồn và thông tin khác: http://wiki.python.org/moin/DictionaryKeys


3

Bởi vì danh sách có thể thay đổi, dictcác khóa (và setthành viên) cần phải có thể băm và việc băm các đối tượng có thể thay đổi là một ý tưởng tồi vì các giá trị băm phải được tính toán dựa trên các thuộc tính cá thể.

Trong câu trả lời này, tôi sẽ đưa ra một số ví dụ cụ thể, hy vọng sẽ tăng thêm giá trị cho các câu trả lời hiện có. Mọi thông tin chi tiết cũng áp dụng cho các phần tử của cơ cấu dữ liệu set.

Ví dụ 1 : băm một đối tượng có thể thay đổi trong đó giá trị băm dựa trên một đặc tính có thể thay đổi của đối tượng.

>>> class stupidlist(list):
...     def __hash__(self):
...         return len(self)
... 
>>> stupid = stupidlist([1, 2, 3])
>>> d = {stupid: 0}
>>> stupid.append(4)
>>> stupid
[1, 2, 3, 4]
>>> d
{[1, 2, 3, 4]: 0}
>>> stupid in d
False
>>> stupid in d.keys()
False
>>> stupid in list(d.keys())
True

Sau khi đột biến stupid, nó không thể được tìm thấy trong dict nữa vì hàm băm đã thay đổi. Chỉ quét tuyến tính trên danh sách các khóa của dict tìm thấy stupid.

Ví dụ 2 : ... nhưng tại sao không chỉ là một giá trị băm không đổi?

>>> class stupidlist2(list):
...     def __hash__(self):
...         return id(self)
... 
>>> stupidA = stupidlist2([1, 2, 3])
>>> stupidB = stupidlist2([1, 2, 3])
>>> 
>>> stupidA == stupidB
True
>>> stupidA in {stupidB: 0}
False

Đó cũng không phải là một ý kiến ​​hay vì các đối tượng bằng nhau nên băm giống hệt nhau để bạn có thể tìm thấy chúng trong dấu dicthoặc set.

Ví dụ 3 : ... ok, còn hàm băm liên tục trên tất cả các trường hợp thì sao ?!

>>> class stupidlist3(list):
...     def __hash__(self):
...         return 1
... 
>>> stupidC = stupidlist3([1, 2, 3])
>>> stupidD = stupidlist3([1, 2, 3])
>>> stupidE = stupidlist3([1, 2, 3, 4])
>>> 
>>> stupidC in {stupidD: 0}
True
>>> stupidC in {stupidE: 0}
False
>>> d = {stupidC: 0}
>>> stupidC.append(5)
>>> stupidC in d
True

Mọi thứ dường như hoạt động như mong đợi, nhưng hãy nghĩ về những gì đang xảy ra: khi tất cả các trường hợp của lớp của bạn tạo ra cùng một giá trị băm, bạn sẽ có xung đột băm bất cứ khi nào có nhiều hơn hai trường hợp làm khóa trong a dicthoặc hiện tại trong a set.

Việc tìm kiếm trường hợp phù hợp với my_dict[key]hoặc key in my_dict(hoặc item in my_set) cần thực hiện nhiều lần kiểm tra tính bình đẳng như các trường hợp stupidlist3trong các khóa của dict (trong trường hợp xấu nhất). Tại thời điểm này, mục đích của từ điển - tra cứu O (1) - hoàn toàn bị đánh bại. Điều này được chứng minh trong thời gian sau (thực hiện với IPython).

Một số thời gian cho Ví dụ 3

>>> lists_list = [[i]  for i in range(1000)]
>>> stupidlists_set = {stupidlist3([i]) for i in range(1000)}
>>> tuples_set = {(i,) for i in range(1000)}
>>> l = [999]
>>> s = stupidlist3([999])
>>> t = (999,)
>>> 
>>> %timeit l in lists_list
25.5 µs ± 442 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
>>> %timeit s in stupidlists_set
38.5 µs ± 61.2 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
>>> %timeit t in tuples_set
77.6 ns ± 1.5 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

Như bạn có thể thấy, kiểm tra tư cách thành viên của chúng tôi stupidlists_setthậm chí còn chậm hơn so với quét tuyến tính trên toàn bộ lists_list, trong khi bạn có thời gian tra cứu siêu nhanh dự kiến ​​(hệ số 500) trong một tập hợp mà không có nhiều va chạm băm.


TL; DR: bạn có thể sử dụng tuple(yourlist)làm dictkhóa, vì các bộ giá trị không thể thay đổi và có thể băm.


>>> x = (1,2,3321321321321,) >>> id (x) 139936535758888 >>> z = (1,2,3321321321321,) >>> id (z) 139936535760544 >>> id ((1, 2,3321321321321,)) 139936535810768 3 giá trị này có cùng giá trị tuple nhưng id khác nhau. Vì vậy, một từ điển với khóa x sẽ không có bất kỳ giá trị nào cho khóa z?
Ashwani

@Ashwani bạn đã dùng thử chưa?
timgeb

Có, Nó đang hoạt động như mong đợi, Tôi nghi ngờ là tất cả các bộ giá trị có cùng giá trị có id khác nhau. Vậy hash này được tính dựa trên cơ sở nào?
Ashwani

@Ashwani Hàm băm của xzgiống nhau. Nếu có điều gì đó không rõ ràng, vui lòng mở một câu hỏi mới.
timgeb

1
@Ashwani hash(x)hash(z).
timgeb

1

Câu trả lời đơn giản cho câu hỏi của bạn là danh sách lớp không triển khai hàm băm phương thức được yêu cầu cho bất kỳ đối tượng nào muốn được sử dụng làm khóa trong từ điển. Tuy nhiên, lý do tại sao hàm băm không được triển khai giống như cách nói với lớp tuple (dựa trên nội dung của vùng chứa) là vì một danh sách có thể thay đổi, do đó việc chỉnh sửa danh sách sẽ yêu cầu hàm băm phải được tính toán lại, điều này có nghĩa là danh sách trong hiện nằm trong nhóm sai trong bảng băm cơ bản. Lưu ý rằng vì bạn không thể sửa đổi một tuple (không thể thay đổi) nên nó không gặp phải vấn đề này.

Cũng cần lưu ý thêm, việc triển khai thực tế của tra cứu đối tượng dựa trên Thuật toán D từ Knuth Vol. 3, Phần 6.4. Nếu bạn có sẵn cuốn sách đó, nó có thể là một cuốn đáng đọc, ngoài ra nếu bạn thực sự, thực sự quan tâm, bạn có thể muốn xem qua nhận xét của nhà phát triển về việc triển khai thực tế của dictobject tại đây. Nó đi vào rất chi tiết về chính xác cách nó hoạt động. Ngoài ra còn có một bài giảng python về cách triển khai các từ điển mà bạn có thể quan tâm. Họ tìm hiểu định nghĩa về khóa và hàm băm là gì trong vài phút đầu tiên.


-1

Theo tài liệu Python 2.7.2:

Một đối tượng có thể được băm nếu nó có giá trị băm không bao giờ thay đổi trong suốt thời gian tồn tại của nó (nó cần một phương thức hash ()) và có thể được so sánh với các đối tượng khác (nó cần một phương thức eq () hoặc cmp ()). Các đối tượng có thể băm được so sánh bằng nhau phải có cùng giá trị băm.

Tính năng hashability làm cho một đối tượng có thể sử dụng được như một khóa từ điển và một thành viên tập hợp, bởi vì các cấu trúc dữ liệu này sử dụng giá trị băm bên trong.

Tất cả các đối tượng tích hợp sẵn của Python đều có thể băm được, trong khi không có vùng chứa có thể thay đổi (chẳng hạn như danh sách hoặc từ điển). Các đối tượng là thể hiện của các lớp do người dùng định nghĩa có thể được băm theo mặc định; tất cả chúng đều so sánh không bằng nhau và giá trị băm của chúng là id () của chúng.

Tuple là bất biến theo nghĩa là bạn không thể thêm, bớt hoặc thay thế các phần tử của nó, nhưng bản thân các phần tử có thể thay đổi được. Giá trị băm của danh sách phụ thuộc vào giá trị băm của các phần tử của nó và do đó nó thay đổi khi bạn thay đổi các phần tử.

Sử dụng id cho các băm danh sách sẽ ngụ ý rằng tất cả các danh sách đều so sánh khác nhau, điều này sẽ gây ngạc nhiên và bất tiện.


1
Điều đó không trả lời câu hỏi, phải không? hash = idkhông phá vỡ bất biến ở cuối đoạn đầu tiên, câu hỏi là tại sao nó không được thực hiện theo cách đó.

@delnan: Tôi đã thêm đoạn cuối để làm rõ.
Nicola Musatti,

-1

Từ điển là một HashMap, nó lưu trữ bản đồ các khóa của bạn, giá trị được chuyển đổi thành khóa mới được băm và ánh xạ giá trị.

một cái gì đó giống như (mã psuedo):

{key : val}  
hash(key) = val

Nếu bạn đang băn khoăn không biết lựa chọn nào có sẵn có thể được sử dụng làm khóa cho từ điển của mình. Sau đó

bất kỳ thứ gì có thể băm (có thể được chuyển đổi thành băm và giữ giá trị tĩnh, tức là không thể thay đổi để tạo khóa băm như đã nêu ở trên) đều đủ điều kiện nhưng vì danh sách hoặc đối tượng tập hợp có thể thay đổi khi đang di chuyển nên băm (khóa) cũng cần để thay đổi chỉ để đồng bộ với danh sách hoặc tập hợp của bạn.

Bạn co thể thử :

hash(<your key here>)

Nếu nó hoạt động tốt, nó có thể được sử dụng làm khóa cho từ điển của bạn hoặc chuyển đổi nó thành một thứ gì đó có thể băm.


Nói ngắn gọn :

  1. Chuyển đổi danh sách đó thành tuple(<your list>).
  2. Chuyển đổi danh sách đó thành str(<your list>).

-1

dictchìa khóa cần phải được băm. Danh sách có thể thay đổi và chúng không cung cấp phương thức băm hợp lệ .

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.