เหตุใดฉันจึงไม่สามารถใช้รายการเป็นคีย์ dict ใน python ได้


106

ฉันสับสนเล็กน้อยเกี่ยวกับสิ่งที่สามารถ / ไม่สามารถใช้เป็นคีย์สำหรับ python dict

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

ทูเปิลจึงเป็นประเภทที่ไม่เปลี่ยนรูป แต่ถ้าฉันซ่อนรายการไว้ข้างในมันก็จะไม่เป็นกุญแจ .. ฉันจะซ่อนรายการภายในโมดูลได้ไม่ยากหรือ?

ฉันมีความคิดที่คลุมเครือว่ากุญแจนั้นจะต้อง "แฮชได้" แต่ฉันจะยอมรับความไม่รู้ของตัวเองเกี่ยวกับรายละเอียดทางเทคนิค ฉันไม่รู้ว่าเกิดอะไรขึ้นที่นี่ จะเกิดอะไรขึ้นถ้าคุณพยายามใช้รายการเป็นคีย์โดยมีแฮชเป็นพูดตำแหน่งหน่วยความจำของพวกเขา


1
นี่คือการอภิปรายที่ดี: stackoverflow.com/questions/2671211/…
Hernan

51
หัวเราะเบา ๆ กับชื่อตัวแปรของคุณ
kindall

คำตอบ:


36

: มีบทความที่ดีในหัวข้อในหลามวิกิพีเดียเป็นทำไมรายการที่ไม่สามารถพจนานุกรมคีย์ ตามที่อธิบายไว้ที่นั่น:

จะเกิดอะไรขึ้นถ้าคุณพยายามใช้รายการเป็นคีย์โดยมีแฮชเป็นพูดตำแหน่งหน่วยความจำของพวกเขา

สามารถทำได้โดยไม่ผิดข้อกำหนด แต่นำไปสู่พฤติกรรมที่ไม่คาดคิด โดยทั่วไปแล้วรายการจะถือว่าเป็นค่าที่ได้มาจากค่าของเนื้อหาเช่นเมื่อตรวจสอบความเท่าเทียมกัน (in-) หลายคนเข้าใจ - คาดหวังว่าคุณสามารถใช้รายการใดก็ได้[1, 2]เพื่อรับคีย์เดียวกันโดยที่คุณจะต้องอยู่รอบ ๆ วัตถุรายการเดียวกัน แต่การค้นหาโดยแบ่งค่าทันทีที่รายการที่ใช้เป็นคีย์ถูกแก้ไขและสำหรับการค้นหาตามเอกลักษณ์ต้องการให้คุณเก็บไว้ในรายการเดียวกันซึ่งไม่จำเป็นสำหรับการดำเนินการรายการทั่วไปอื่น ๆ (อย่างน้อยก็ไม่มีอะไรที่ฉันคิดได้ ).

ออบเจ็กต์อื่น ๆ เช่นโมดูลและobjectสร้างข้อตกลงที่ใหญ่กว่ามากจากเอกลักษณ์ของอ็อบเจ็กต์อยู่ดี (ครั้งสุดท้ายที่คุณมีอ็อบเจ็กต์โมดูลที่แตกต่างกันสองตัวเรียกว่าเมื่อsysใด) และจะถูกนำมาเปรียบเทียบกัน ดังนั้นจึงน่าแปลกใจน้อยกว่า - หรือคาดหวัง - เมื่อใช้เป็นคีย์ dict ให้เปรียบเทียบตามตัวตนในกรณีนั้นเช่นกัน


33

เหตุใดฉันจึงไม่สามารถใช้รายการเป็นคีย์ dict ใน python ได้

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

(สำหรับใครที่สะดุดกับคำถามนี้ที่กำลังมองหาวิธีแก้ไข)

ตามที่ผู้อื่นอธิบายไว้ที่นี่แน่นอนว่าคุณทำไม่ได้ อย่างไรก็ตามคุณสามารถใช้การแสดงสตริงแทนได้หากคุณต้องการใช้รายการของคุณจริงๆ


6
ขออภัยฉันไม่เห็นประเด็นของคุณจริงๆ ไม่ต่างอะไรกับการใช้ตัวอักษรสตริงเป็นคีย์
Wim

12
จริง; ฉันเพิ่งเห็นคำตอบมากมายที่อธิบายว่าทำไมคุณไม่สามารถใช้รายการในรูปแบบของ 'คีย์ต้องแฮชได้' ซึ่งเป็นความจริงมากจนฉันต้องการแนะนำวิธีการแก้ไขในกรณีที่มีคน (ใหม่) กำลังมองหา ...
Remi

5
ทำไมไม่เพียงแค่แปลงรายการเป็นทูเพิล? ทำไมต้องแปลงเป็นสตริง? ถ้าคุณใช้ tuple __eq__มันจะทำงานได้อย่างถูกต้องกับการเรียนที่มีวิธีการเปรียบเทียบที่กำหนดเอง แต่ถ้าคุณแปลงเป็นสตริงทุกอย่างจะถูกเปรียบเทียบด้วยการแทนค่าสตริง
Aran-Fey

จุดดี @ Aran-Fey. ตรวจสอบให้แน่ใจว่าองค์ประกอบใด ๆ ในทูเปิลนั้นสามารถแฮชได้ เช่นทูเปิล ([[1,2], [2,3]]) เป็นคีย์จะไม่ทำงานเนื่องจากองค์ประกอบของทูเปิลยังคงเป็นรายการ
Remi

20

พบว่าคุณสามารถเปลี่ยนรายการเป็นทูเปิลจากนั้นใช้เป็นคีย์ได้

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

ทำงานอย่างมีเสน่ห์!
Tabz

16

ปัญหาคือสิ่งที่สองไม่เปลี่ยนรูปและรายการไม่เปลี่ยนรูป พิจารณาสิ่งต่อไปนี้

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

ควรd[li]กลับอะไร เป็นรายการเดียวกันหรือไม่? แล้วไงd[[1,2,3]]? มีค่าเหมือนกัน แต่เป็นรายการที่แตกต่างกัน?

ท้ายที่สุดแล้วไม่มีคำตอบที่น่าพอใจ ตัวอย่างเช่นหากคีย์เดียวที่ใช้งานได้คือคีย์ดั้งเดิมหากคุณไม่มีการอ้างอิงถึงคีย์นั้นคุณจะไม่สามารถเข้าถึงค่าได้อีก ด้วยคีย์อื่น ๆ ที่อนุญาตคุณสามารถสร้างคีย์โดยไม่ต้องอ้างอิงกับคีย์ดั้งเดิม

หากคำแนะนำของฉันทั้งสองได้ผลแสดงว่าคุณมีคีย์ที่แตกต่างกันซึ่งส่งคืนค่าเดียวกันซึ่งน่าแปลกใจกว่าเล็กน้อย หากมีเพียงเนื้อหาต้นฉบับเท่านั้นที่ใช้งานได้คีย์ของคุณจะเสียไปอย่างรวดเร็วเนื่องจากมีการแก้ไขรายการ


ใช่มันเป็นรายการเดียวกันดังนั้นฉันคาดว่าd[li]จะยังคงอยู่ 5 d[[1,2,3]]จะอ้างถึงวัตถุรายการอื่นเป็นคีย์ดังนั้นมันจะเป็น KeyError ฉันยังไม่เห็นปัญหาใด ๆ เลย .. ยกเว้นว่าการปล่อยให้คีย์เก็บขยะอาจทำให้ไม่สามารถเข้าถึงค่า dict บางค่าได้ แต่ที่เป็นปัญหาในทางปฏิบัติไม่เป็นปัญหาตรรกะ ..
Wim

@wim: d[list(li)]การเป็น KeyError เป็นส่วนหนึ่งของปัญหา ในเกือบทุกกรณีใช้งานอื่น ๆ , liจะแยกไม่ออกจากรายการใหม่ที่มีเนื้อหาเหมือนกัน ใช้งานได้ แต่สวนทางกับหลาย ๆ คน นอกจากนั้นเมื่อเป็นครั้งสุดท้ายที่คุณจริงๆมีการใช้รายการเป็นคีย์ Dict หรือไม่? กรณีการใช้งานเดียวที่ฉันสามารถจินตนาการได้คือเมื่อคุณแฮชทุกอย่างด้วยตัวตนและในกรณีนี้คุณควรทำเช่นนั้นแทนที่จะพึ่งพา__hash__และ__eq__ยึดตามตัวตน

@ เดลแนนปัญหาแค่ว่ามันจะไม่มีประโยชน์มากนักเพราะภาวะแทรกซ้อนดังกล่าว? หรือมีเหตุผลบางอย่างที่ทำให้มันทำลายคำสั่งได้จริงหรือ?
Wim

2
@wim: อย่างหลัง. ตามที่ระบุไว้ในคำตอบของฉันมันไม่ได้ผิดข้อกำหนดเกี่ยวกับคีย์ dict แต่มีแนวโน้มที่จะทำให้เกิดปัญหามากกว่าที่จะแก้ได้

1
@delnan - คุณตั้งใจจะพูดว่า 'อดีต'
Jason

9

นี่คือคำตอบhttp://wiki.python.org/moin/DictionaryKeys

จะเกิดอะไรขึ้นถ้าคุณพยายามใช้รายการเป็นคีย์โดยมีแฮชเป็นพูดตำแหน่งหน่วยความจำของพวกเขา

การค้นหารายการที่แตกต่างกันที่มีเนื้อหาเดียวกันจะให้ผลลัพธ์ที่แตกต่างกันแม้ว่าการเปรียบเทียบรายการที่มีเนื้อหาเดียวกันจะระบุว่าเทียบเท่า

สิ่งที่เกี่ยวกับการใช้ลิสต์ลิเทอรัลในการค้นหาพจนานุกรม?


4

เนื่องจากรายการสามารถเปลี่ยนแปลงได้dictคีย์ (และsetสมาชิก) จึงจำเป็นต้องมีการแฮชและการแฮชอ็อบเจ็กต์ที่เปลี่ยนแปลงได้จึงเป็นความคิดที่ไม่ดีเนื่องจากควรคำนวณค่าแฮชบนพื้นฐานของแอ็ตทริบิวต์อินสแตนซ์

ในคำตอบนี้ฉันจะยกตัวอย่างที่เป็นรูปธรรมหวังว่าจะเพิ่มมูลค่าให้กับคำตอบที่มีอยู่ ข้อมูลเชิงลึกทั้งหมดใช้กับองค์ประกอบของโครงสร้างข้อมูลsetเช่นกัน

ตัวอย่างที่ 1 : การแฮชอ็อบเจ็กต์ที่เปลี่ยนแปลงได้โดยที่ค่าแฮชจะขึ้นอยู่กับลักษณะที่ไม่แน่นอนของอ็อบเจ็กต์

>>> 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

หลังจากกลายพันธุ์stupidแล้วจะไม่พบใน dict อีกต่อไปเนื่องจากแฮชเปลี่ยนไป เพียงสแกนเส้นตรงในรายการ Dict stupidของคีย์ที่พบ

ตัวอย่างที่ 2 : ... แต่ทำไมไม่ใช่แค่ค่าแฮชคงที่ล่ะ?

>>> 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

นั่นไม่ใช่ความคิดที่ดีเช่นกันเพราะวัตถุเท่ากับควรสับเหมือนเช่นที่คุณสามารถพบพวกเขาในหรือdictset

ตัวอย่างที่ 3 : ... ตกลงแล้วแฮชคงที่ในทุกอินสแตนซ์ล่ะ?!

>>> 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

สิ่งต่างๆดูเหมือนจะเป็นไปตามที่คาดไว้ แต่ลองนึกถึงสิ่งที่เกิดขึ้น: เมื่อทุกอินสแตนซ์ในชั้นเรียนของคุณสร้างค่าแฮชเท่ากันคุณจะมีการชนกันของแฮชเมื่อใดก็ตามที่มีอินสแตนซ์มากกว่าสองอินสแตนซ์เป็นคีย์ในdictหรืออยู่ในไฟล์set.

การค้นหาอินสแตนซ์ที่ถูกต้องด้วยmy_dict[key]หรือkey in my_dict(หรือitem in my_set) จำเป็นต้องทำการตรวจสอบความเท่าเทียมกันให้มากที่สุดเท่าที่มีอยู่stupidlist3ในคีย์ของ dict (ในกรณีที่เลวร้ายที่สุด) ณ จุดนี้จุดประสงค์ของพจนานุกรม - การค้นหา O (1) - พ่ายแพ้อย่างสมบูรณ์ สิ่งนี้แสดงให้เห็นในการกำหนดเวลาต่อไปนี้ (ทำด้วย IPython)

การกำหนดเวลาบางอย่างสำหรับตัวอย่างที่ 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)

อย่างที่คุณเห็นการทดสอบความเป็นสมาชิกในของเราstupidlists_setนั้นช้ากว่าการสแกนเชิงเส้นในภาพรวมlists_listในขณะที่คุณมีเวลาค้นหาที่รวดเร็วเป็นพิเศษ (ปัจจัย 500) ในชุดโดยไม่มีการชนกันของแฮช


TL; DR: คุณสามารถใช้tuple(yourlist)เป็นdictคีย์ได้เนื่องจากทูเปิลไม่เปลี่ยนรูปและล้างทำความสะอาดได้


>>> x = (1,2,3321321321321,) >>> id (x) 139936535758888 >>> z = (1,2,3321321321321,) >>> id (z) 139936535760544 >>> id ((1, 2,3321321321321,)) 139936535810768 ทั้ง 3 นี้มีค่าทูเพิลเหมือนกัน แต่ id ต่างกัน ดังนั้นพจนานุกรมที่มีคีย์ x จะไม่มีค่าสำหรับคีย์ z?
Ashwani

@ คุณ Ashwani ลองดูไหม
timgeb

ใช่มันใช้งานได้ตามที่คาดไว้ข้อสงสัยของฉันคือสิ่งที่มีค่าเดียวกันทั้งหมดมีรหัสต่างกัน ดังนั้นแฮชนี้คำนวณจากอะไร?
Ashwani

@Ashwani แฮชของxและzก็เหมือนกัน หากมีบางอย่างไม่ชัดเจนโปรดเปิดคำถามใหม่
timgeb

1
@Ashwani hash(x)และhash(z).
timgeb

3

กันสาดของคุณอยู่ที่นี่:

ทำไมรายการถึงไม่เป็นคีย์พจนานุกรม

ผู้ที่มาใหม่ใน Python มักจะสงสัยว่าทำไมในขณะที่ภาษามีทั้งทูเพิลและประเภทรายการสิ่งทูเพิลสามารถใช้เป็นคีย์พจนานุกรมได้ในขณะที่รายการไม่ได้ นี่เป็นการตัดสินใจออกแบบโดยเจตนาและสามารถอธิบายได้ดีที่สุดโดยการทำความเข้าใจก่อนว่าพจนานุกรม Python ทำงานอย่างไร

ที่มาและข้อมูลเพิ่มเติม: http://wiki.python.org/moin/DictionaryKeys


3

คำตอบง่ายๆสำหรับคำถามของคุณคือรายการคลาสไม่ได้ใช้วิธีแฮชซึ่งจำเป็นสำหรับอ็อบเจ็กต์ใด ๆ ที่ต้องการใช้เป็นคีย์ในพจนานุกรม อย่างไรก็ตามสาเหตุที่ไม่ใช้แฮชในลักษณะเดียวกับที่กล่าวว่าคลาสทูเปิล (ตามเนื้อหาของคอนเทนเนอร์) เป็นเพราะรายการไม่แน่นอนดังนั้นการแก้ไขรายการจึงจำเป็นต้องมีการคำนวณแฮชใหม่ซึ่งอาจหมายถึงรายการใน ตอนนี้อยู่ในที่เก็บข้อมูลที่ไม่ถูกต้องภายในตารางแฮชของ underling โปรดทราบว่าเนื่องจากคุณไม่สามารถแก้ไขทูเปิล (ไม่เปลี่ยนรูป) ได้จึงไม่พบปัญหานี้

ดังที่ทราบกันดีว่าการนำไปใช้จริงของการค้นหาวัตถุในรูปแบบนั้นเป็นไปตามอัลกอริทึม D จาก Knuth Vol 3, วินาที 6.4. หากคุณมีหนังสือเล่มนั้นให้อ่านอาจเป็นเรื่องที่ควรค่าแก่การอ่านนอกจากนี้หากคุณสนใจจริงๆคุณอาจต้องการดูความคิดเห็นของนักพัฒนาเกี่ยวกับการนำวัตถุไปใช้จริงที่นี่ มันลงรายละเอียดมากว่ามันทำงานอย่างไร นอกจากนี้ยังมีการบรรยาย python เกี่ยวกับการใช้พจนานุกรมที่คุณอาจสนใจพวกเขาจะอธิบายถึงคำจำกัดความของคีย์และสิ่งที่แฮชคืออะไรในช่วงสองสามนาทีแรก


-1

ตามเอกสาร Python 2.7.2:

อ็อบเจ็กต์สามารถแฮชได้หากมีค่าแฮชซึ่งไม่เคยเปลี่ยนแปลงตลอดอายุการใช้งาน (ต้องการแฮช () วิธีการ) และสามารถเปรียบเทียบกับอ็อบเจ็กต์อื่น ๆ ได้ (ต้องใช้eq () หรือcmp () method) อ็อบเจ็กต์ที่แฮชได้ซึ่งเปรียบเทียบเท่ากับต้องมีค่าแฮชเท่ากัน

ความสามารถในการแฮชทำให้อ็อบเจ็กต์สามารถใช้เป็นคีย์พจนานุกรมและสมาชิกชุดได้เนื่องจากโครงสร้างข้อมูลเหล่านี้ใช้ค่าแฮชเป็นการภายใน

อ็อบเจ็กต์ในตัวที่ไม่เปลี่ยนรูปของ Python ทั้งหมดสามารถแฮชได้ในขณะที่ไม่มีคอนเทนเนอร์ที่เปลี่ยนแปลงได้ (เช่นรายการหรือพจนานุกรม) อ็อบเจ็กต์ที่เป็นอินสแตนซ์ของคลาสที่ผู้ใช้กำหนดสามารถแฮชได้ตามค่าเริ่มต้น พวกเขาทั้งหมดเปรียบเทียบไม่เท่ากันและค่าแฮชคือ id ()

ทูเปิลไม่เปลี่ยนรูปในแง่ที่คุณไม่สามารถเพิ่มลบหรือแทนที่องค์ประกอบได้ แต่องค์ประกอบนั้นอาจเปลี่ยนแปลงได้ ค่าแฮชของรายการจะขึ้นอยู่กับค่าแฮชขององค์ประกอบดังนั้นจึงเปลี่ยนแปลงเมื่อคุณเปลี่ยนองค์ประกอบ

การใช้ id สำหรับแฮชรายการจะบ่งบอกว่ารายการทั้งหมดเปรียบเทียบต่างกันซึ่งน่าแปลกใจและไม่สะดวก


1
ที่ไม่ตอบคำถามใช่หรือไม่? hash = idไม่ทำลายค่าคงที่ในตอนท้ายของย่อหน้าแรกคำถามคือเหตุใดจึงไม่ทำเช่นนั้น

@ เดลแนน: ฉันเพิ่มย่อหน้าสุดท้ายเพื่อชี้แจง
Nicola Musatti

-1

พจนานุกรมคือ HashMap ที่เก็บแผนที่คีย์ของคุณค่าที่แปลงเป็นคีย์ใหม่ที่แฮชและการจับคู่ค่า

บางอย่างเช่น (รหัส psuedo):

{key : val}  
hash(key) = val

หากคุณสงสัยว่ามีตัวเลือกใดบ้างที่สามารถใช้เป็นกุญแจสำคัญสำหรับพจนานุกรมของคุณ แล้ว

สิ่งใดก็ตามที่แฮชได้ (สามารถแปลงเป็นแฮชและเก็บค่าคงที่เช่นไม่เปลี่ยนรูปเพื่อสร้างคีย์แฮชตามที่ระบุไว้ข้างต้น)มีสิทธิ์ได้ แต่เนื่องจากรายการหรือเซ็ตอ็อบเจ็กต์อาจแตกต่างกันไปในระหว่างเดินทางดังนั้นแฮช (คีย์) ก็ควรต้องการเช่นกัน แตกต่างกันไปเพื่อให้สอดคล้องกับรายการหรือชุดของคุณ

คุณสามารถลอง :

hash(<your key here>)

หากใช้งานได้ดีก็สามารถใช้เป็นคีย์สำหรับพจนานุกรมของคุณหรือแปลงเป็นสิ่งที่ล้างทำความสะอาดได้


สั้น:

  1. แปลงรายการนั้นเป็น tuple(<your list>)นั้นเป็น.
  2. แปลงรายการstr(<your list>)นั้นเป็น.

-1

dictต้องมีกุญแจที่สามารถทำความสะอาดได้ รายการไม่แน่นอนและไม่ได้ระบุที่ถูกต้องวิธีแฮชที่

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.