พจนานุกรมสั่งใน Python 3.6+ หรือไม่


467

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

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

นี่คือข้อความจากเอกสาร:

dict()ตอนนี้ใช้เป็น“เล็ก” เป็นตัวแทนผู้บุกเบิกโดย PyPy การใช้หน่วยความจำของ dict ใหม่ () อยู่ระหว่าง 20% ถึง 25% เล็กกว่าเมื่อเปรียบเทียบกับ Python 3.5 PEP 468 (การรักษาลำดับของ ** kwargs ในฟังก์ชั่น) ถูกใช้งานโดยสิ่งนี้ ด้านการรักษาลำดับของการใช้งานใหม่นี้ถือว่าเป็นรายละเอียดการใช้งานและไม่ควรพึ่งพา (อาจเปลี่ยนแปลงได้ในอนาคต แต่เป็นที่ต้องการที่จะมีการใช้งาน dict ใหม่นี้ในภาษาสำหรับรุ่นไม่กี่ก่อนที่จะเปลี่ยนข้อมูลจำเพาะภาษา เพื่อมอบอำนาจความหมายเพื่อรักษาคำสั่งซื้อสำหรับการใช้งาน Python ปัจจุบันและอนาคตทั้งหมดนี้ยังช่วยรักษาความเข้ากันได้ย้อนหลังกับภาษารุ่นเก่าที่คำสั่งการสุ่มซ้ำยังคงมีผลเช่น Python 3.5) (สนับสนุนโดย INADA Naoki ในปัญหา 27350 แนวคิดที่เสนอโดย Raymond Hettinger )

อัปเดตธันวาคม 2560: รับประกันdictการแทรกคำสั่งการรักษาสำหรับ Python 3.7


2
ดูกระทู้นี้ในรายการส่งเมล์ของ Python- mail : mail.python.org/pipermail/python-dev2016-September/146327.htmlหากคุณยังไม่เคยเห็น เป็นการพูดคุยเกี่ยวกับหัวข้อเหล่านี้
mgc

1
ถ้า kwargs ตอนนี้ควรจะสั่ง (ซึ่งเป็นความคิดที่ดี) และ kwargs เป็น dict ไม่ใช่ OrderedDict แล้วฉันเดาว่าใครจะคิดว่าคีย์ dict จะยังคงอยู่ในเวอร์ชันอนาคตของ Python แม้ว่าเอกสารจะระบุเป็นอย่างอื่น
Dmitriy Sintsov

4
@DmitriySintsov ไม่อย่าทำข้อสันนิษฐานนั้น นี้เป็นปัญหาขึ้นมาในระหว่างการเขียน PEP ที่ใช้กำหนดลำดับการรักษาคุณสมบัติของ**kwargsและเป็นเช่นถ้อยคำที่ใช้เป็นทูต: **kwargsในลายเซ็นฟังก์ชั่นมีการประกันตอนนี้จะแทรกคำสั่งรักษาการทำแผนที่ พวกเขาใช้คำว่าการจับคู่เพื่อไม่บังคับให้การนำไปใช้งานอื่น ๆ เพื่อสั่งให้สั่งการ (และใช้เป็นการOrderedDictภายใน) และเป็นวิธีการส่งสัญญาณว่าสิ่งนี้ไม่ควรขึ้นอยู่กับความจริงที่dictว่าไม่ได้สั่ง
Dimitris Fasarakis Hilliard

7
คำอธิบายวิดีโอที่ดีจาก Raymond Hettinger
Alex

1
@wazoox การสั่งซื้อและความซับซ้อนของ hashmap ไม่ได้เปลี่ยนแปลง การเปลี่ยนแปลงทำให้ hashmap มีขนาดเล็กลงโดยสิ้นเปลืองเนื้อที่น้อยลงและพื้นที่ที่บันทึกไว้ (ปกติ?) มากกว่าที่ใช้อาร์เรย์เสริม เร็วขึ้นเล็กลงสั่ง - คุณจะได้รับทั้งหมด 3
John La Rooy

คำตอบ:


510

พจนานุกรมสั่งใน Python 3.6+ หรือไม่

พวกเขาจะแทรกสั่งซื้อ [1] ในฐานะที่เป็นของงูใหญ่ 3.6 สำหรับการดำเนินงานของงูใหญ่ CPython พจนานุกรมจำลำดับของรายการแทรก นี่คือการพิจารณารายละเอียดการดำเนินงานในหลาม 3.6 ; คุณจำเป็นต้องใช้OrderedDictถ้าคุณต้องการให้การสั่งซื้อแบบแทรกนั้นรับประกันได้กับการใช้งานอื่น ๆ ของ Python (และพฤติกรรมการสั่งซื้ออื่น ๆ[1] )

ในฐานะของ Python 3.7นี่ไม่ใช่รายละเอียดการใช้งานอีกต่อไปและจะกลายเป็นคุณสมบัติภาษาแทน จากข้อความ python-dev โดย GvR :

ทำให้เป็นเช่นนั้น "Dict เก็บคำสั่งแทรก" เป็นคำสั่ง ขอบคุณ!

นี้หมายความว่าคุณสามารถขึ้นอยู่กับมัน การใช้งานอื่น ๆ ของ Python จะต้องเสนอพจนานุกรมสั่งแทรกหากพวกเขาต้องการที่จะใช้งานที่สอดคล้องของ Python 3.7


การใช้3.6พจนานุกรมPython ทำงานได้ดีกว่า[2]ที่เก่ากว่าในขณะที่รักษาลำดับองค์ประกอบ

เป็นหลักโดยการรักษาสองอาร์เรย์

  • อาร์เรย์แรกdk_entriesถือรายการ ( จากประเภทPyDictKeyEntry ) สำหรับพจนานุกรมตามลำดับที่แทรก การรักษาคำสั่งซื้อทำได้โดยการต่อเติมเป็นอาร์เรย์ที่มีการแทรกรายการใหม่ที่ส่วนท้ายเสมอ (ลำดับการแทรก)

  • ประการที่สองdk_indicesถือดัชนีสำหรับdk_entriesอาร์เรย์ (นั่นคือค่าที่ระบุตำแหน่งของรายการที่สอดคล้องกันdk_entries) อาร์เรย์นี้ทำหน้าที่เป็นตารางแฮช เมื่อคีย์ถูกแฮชจะนำไปสู่หนึ่งในดัชนีที่จัดเก็บในและรายการที่สอดคล้องกันคือความจริงโดยการจัดทำดัชนีdk_indices dk_entriesเนื่องจากดัชนีจะถูกเก็บไว้ชนิดของอาร์เรย์นี้จึงขึ้นอยู่กับขนาดโดยรวมของพจนานุกรม (ตั้งแต่ประเภทint8_t( 1ไบต์) ถึงint32_t/ int64_t( 4/ 8bytes) บน32/ 64สร้างบิต)

ในการใช้งานก่อนหน้านี้ต้องมีการจัดสรรประเภทPyDictKeyEntryและขนาดของกระจัดกระจาย dk_sizeโชคร้ายก็ยังส่งผลให้ในหลายพื้นที่ว่างตั้งแต่อาร์เรย์ที่ไม่ได้รับอนุญาตให้เป็นมากกว่า2/3 * dk_sizeเต็มรูปแบบเพื่อเหตุผลด้านประสิทธิภาพ (และพื้นที่ว่างยังคงมีPyDictKeyEntryขนาด!)

กรณีนี้ไม่ได้ในขณะนี้ตั้งแต่เพียงต้องป้อนข้อมูลจะถูกเก็บไว้ (ผู้ที่ได้รับการแทรก) และอาร์เรย์เบาบางชนิดintX_t( Xขึ้นอยู่กับขนาด Dict) 2/3 * dk_sizeเต็มจะถูกเก็บไว้ พื้นที่ว่างเปล่าเปลี่ยนจากประเภทการPyDictKeyEntryintX_t

ดังนั้นการสร้างอาเรย์แบบเบาบางPyDictKeyEntryจึงเป็นความต้องการของหน่วยความจำมากกว่าอาเรย์แบบเบาบางสำหรับการจัดเก็บints

คุณสามารถดูบทสนทนาเต็มรูปแบบใน Python-Devเกี่ยวกับคุณลักษณะนี้หากสนใจมันเป็นการอ่านที่ดี


ในข้อเสนอดั้งเดิมที่ทำโดย Raymond Hettingerการสร้างภาพข้อมูลของโครงสร้างข้อมูลที่ใช้สามารถเห็นได้ซึ่งรวบรวมส่วนสำคัญของความคิด

ตัวอย่างเช่นพจนานุกรม:

d = {'timmy': 'red', 'barry': 'green', 'guido': 'blue'}

ขณะนี้ถูกเก็บไว้เป็น [keyhash, key, value]:

entries = [['--', '--', '--'],
           [-8522787127447073495, 'barry', 'green'],
           ['--', '--', '--'],
           ['--', '--', '--'],
           ['--', '--', '--'],
           [-9092791511155847987, 'timmy', 'red'],
           ['--', '--', '--'],
           [-6480567542315338377, 'guido', 'blue']]

แต่ควรจัดระเบียบข้อมูลดังต่อไปนี้:

indices =  [None, 1, None, None, None, 0, None, 2]
entries =  [[-9092791511155847987, 'timmy', 'red'],
            [-8522787127447073495, 'barry', 'green'],
            [-6480567542315338377, 'guido', 'blue']]

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


[1]: ฉันพูดว่า "การสั่งการแทรก" และไม่ใช่ "สั่ง" เนื่องจากมีการมีอยู่ของ OrderedDict "สั่ง" จะแนะนำพฤติกรรมเพิ่มเติมที่dictวัตถุไม่ได้จัดเตรียมไว้ OrderedDicts ย้อนกลับให้การสั่งซื้อวิธีที่สำคัญและส่วนใหญ่ให้การทดสอบความเสมอภาคสั่งเซนซีฟ ( ==, !=) dicts ไม่ได้เสนอพฤติกรรม / วิธีการใด ๆ


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

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


15
ดังนั้นจะเกิดอะไรขึ้นเมื่อมีการลบรายการ เป็นentriesรายการปรับขนาด? หรือพื้นที่ว่างถูกเก็บไว้? หรือมันถูกบีบอัดเป็นครั้งคราว?
njzk2

18
@ njzk2 เมื่อรายการถูกลบดัชนีที่สอดคล้องกันจะถูกแทนที่DKIX_DUMMYด้วยค่าของ-2และรายการในentryอาร์เรย์ที่ถูกแทนที่ด้วยNULLเมื่อทำการแทรกค่าใหม่จะถูกผนวกเข้ากับอาร์เรย์รายการ แต่ยังไม่สามารถมองเห็นได้ แต่ค่อนข้างแน่ใจว่าเมื่อดัชนีเต็มเกินกว่าการ2/3ปรับขนาดตามเกณฑ์จะดำเนินการ สิ่งนี้สามารถนำไปสู่การหดตัวแทนที่จะเติบโตหากมีหลายDUMMYรายการ
Dimitris Fasarakis Hilliard

3
@Chris_Rands Nope การถดถอยที่เกิดขึ้นจริงเท่านั้นที่ผมเคยเห็นเป็นเกี่ยวกับการติดตามในข้อความโดยวิกเตอร์ นอกจาก microbenchmark นั้นฉันไม่เคยเห็นปัญหา / ข้อความอื่นใดที่บ่งบอกถึงความแตกต่างของความเร็วที่ร้ายแรงในการทำงานในชีวิตจริง มีสถานที่ที่ dict ใหม่อาจแนะนำการถดถอยเล็กน้อย (เช่นการค้นหาคีย์) ในขณะที่อยู่ในสถานที่อื่น ๆ (การคำนวณซ้ำและการปรับขนาดให้นึกถึง) การเพิ่มประสิทธิภาพจะมีขึ้น
Dimitris Fasarakis Hilliard

3
การแก้ไขในส่วนที่ปรับขนาด : พจนานุกรมจะไม่ปรับขนาดเมื่อคุณลบรายการพวกเขาจะคำนวณอีกครั้งเมื่อคุณแทรกใหม่ ดังนั้นถ้า dict ถูกสร้างขึ้นด้วยd = {i:i for i in range(100)}และคุณ.popทุกรายการโดยไม่มีการแทรกขนาดจะไม่เปลี่ยนแปลง เมื่อคุณเพิ่มเข้าไปอีกครั้ง d[1] = 1ขนาดที่เหมาะสมจะถูกคำนวณและ dict จะปรับขนาด
Dimitris Fasarakis Hilliard

6
@Chris_Rands ฉันค่อนข้างแน่ใจว่าจะอยู่ สิ่งที่เป็นและเหตุผลที่ว่าทำไมผมเปลี่ยนคำตอบของฉันที่จะเอางบผ้าห่มเกี่ยวกับ ' dictถูกสั่ง' dicts ยังไม่ได้รับคำสั่งในความรู้สึกOrderedDictที่มี ประเด็นที่น่าสังเกตคือความเท่าเทียมกัน dicts มีความรู้สึกการสั่งซื้อ==, OrderedDicts มีคำสั่งคนที่มีความละเอียดอ่อน การดัมพ์OrderedDictและการเปลี่ยนdictsเป็นตอนนี้มีการเปรียบเทียบการสั่งซื้อที่ละเอียดอ่อนอาจนำไปสู่การแตกจำนวนมากในรหัสเก่า ฉันคาดเดาสิ่งเดียวที่อาจเปลี่ยนแปลงเกี่ยวกับOrderedDicts คือการนำไปใช้งาน
Dimitris Fasarakis Hilliard

66

ด้านล่างคือการตอบคำถามแรกเริ่ม:

ฉันควรใช้ dictหรือOrderedDictใน Python 3.6 หรือไม่

ฉันคิดว่าประโยคนี้จากเอกสารจริงเพียงพอที่จะตอบคำถามของคุณ

ด้านการรักษาคำสั่งซื้อของการใช้งานใหม่นี้ถือว่าเป็นรายละเอียดการใช้งานและไม่ควรเชื่อถือ

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

ทำรหัสของคุณพิสูจน์อนาคต :)

มีการอภิปรายเกี่ยวกับที่นี่

แก้ไข: Python 3.7 จะทำให้สิ่งนี้เป็นคุณลักษณะที่ เห็น


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

3
ฉันไม่แน่ใจเกี่ยวกับการแก้ไขข้อแม้ของคุณ เนื่องจากการรับประกันใช้กับ Python 3.7 เท่านั้นฉันคิดว่าคำแนะนำสำหรับ Python 3.6 นั้นไม่เปลี่ยนแปลงนั่นคือการสั่ง dicts ใน CPython แต่ไม่นับบนมัน
Chris_Rands

25

อัปเดต: Guido van Rossum ประกาศในรายการส่งเมลว่า Python 3.7 dicts ในการใช้งาน Python ทั้งหมดจะต้องรักษาลำดับการแทรก


2
ตอนนี้การสั่งซื้อที่สำคัญคือมาตรฐานอย่างเป็นทางการวัตถุประสงค์ของ OrderedDict คืออะไร หรือตอนนี้ซ้ำซ้อนหรือไม่
Jonny Waffles

2
ฉันเดา OrderedDict จะไม่ซ้ำซ้อนเพราะมันมีmove_to_endวิธีการและความเท่าเทียมกันของมันคือความไวในการสั่งซื้อ: docs.python.org/3/library/ … ดูหมายเหตุเกี่ยวกับคำตอบของ Jim Fasarakis Hilliard
fjsj

@JonnyWaffles ดูคำตอบของ Jim และคำถาม & คำตอบstackoverflow.com/questions/50872498/ นี้
Chris_Rands

3
หากคุณต้องการให้โค้ดทำงานเหมือนกันใน 2.7 และ 3.6 / 3.7 + คุณต้องใช้ OrderedDict
boatcoder

3
มีแนวโน้มว่าจะมี "UnorderedDict" ในไม่ช้าสำหรับผู้ที่ต้องการรบกวนความปลอดภัยด้วยเหตุผลด้านความปลอดภัย p
ZF007

9

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

งูหลาม 3.8 จะไม่นำออกค่อนข้าง แต่มันก็จะรวมถึงreversed()ฟังก์ชั่นในพจนานุกรม OrderedDict(ลบแตกต่างจากที่อื่น

Dict และ dictviews ขณะนี้สามารถใช้คำสั่งแทรกได้โดยใช้ reverse () (ร่วมโดยRémi Lapeyre ใน bpo-33462) ดูว่ามีอะไรใหม่ใน python 3.8

ฉันไม่เห็นการกล่าวถึงผู้ประกอบการความเสมอภาคหรือคุณลักษณะอื่น ๆ ของOrderedDictดังนั้นพวกเขายังคงไม่เหมือนกันทั้งหมด

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