पायथन के अंतर्निहित शब्‍दों को कैसे लागू किया गया है?


294

क्या किसी को पता है कि अजगर के लिए शब्दकोश प्रकार में कैसे कार्यान्वित किया जाता है? मेरी समझ यह है कि यह किसी प्रकार की हैश तालिका है, लेकिन मैं किसी भी प्रकार का निश्चित उत्तर नहीं पा सका हूं।


4
यहां 2.7 से लेकर 3.6 तक पायथन डिक्शनरी के बारे में एक व्यावहारिक बात है। लिंक
सॉरेन

जवाबों:


494

यहां पायथन डाइक के बारे में सब कुछ है जिसे मैं एक साथ रखने में सक्षम था (शायद किसी को भी अधिक जानना चाहूंगा, लेकिन जवाब व्यापक है)।

  • पायथन शब्दकोशों को हैश टेबल के रूप में लागू किया जाता है ।
  • हैश तालिकाओं को हैश टकरावों के लिए अनुमति देनी चाहिए, भले ही दो अलग-अलग कुंजियों में समान हैश मान हो, तालिका के कार्यान्वयन में कुंजी और मूल्य जोड़े को असंदिग्ध रूप से सम्मिलित करने और प्राप्त करने की रणनीति होनी चाहिए।
  • पायथन हैश टकराने (नीचे समझाया गया) को हल करने के लिए खुले पतेdict का उपयोग करता है (देखें हुक्मबॉक्ट । देखें : 296-297 )।
  • पायथन हैश टेबल सिर्फ मेमोरी का एक सन्निहित ब्लॉक है (एक सरणी की तरह, ताकि आप O(1)सूचकांक द्वारा एक लुकअप कर सकें )।
  • तालिका में प्रत्येक स्लॉट एक और केवल एक प्रविष्टि संग्रहीत कर सकता है। यह महत्वपूर्ण है।
  • तालिका में प्रत्येक प्रविष्टि वास्तव में तीन मूल्यों का एक संयोजन है: <हैश, कुंजी, मूल्य> । यह एक सी संरचना के रूप में लागू किया गया है (देखें डिक्टोबोबे.ह: 51-56 )।
  • नीचे दिया गया आंकड़ा पायथन हैश तालिका का एक तार्किक प्रतिनिधित्व है। नीचे दी गई आकृति में, 0, 1, ..., i, ...बाईं ओर हैश तालिका में स्लॉट्स के सूचक हैं (वे सिर्फ उदाहरण के लिए हैं और स्पष्ट रूप से तालिका के साथ संग्रहीत नहीं हैं!)।

    # Logical model of Python Hash table
    -+-----------------+
    0| <hash|key|value>|
    -+-----------------+
    1|      ...        |
    -+-----------------+
    .|      ...        |
    -+-----------------+
    i|      ...        |
    -+-----------------+
    .|      ...        |
    -+-----------------+
    n|      ...        |
    -+-----------------+
    
  • जब एक नया तानाशाही शुरू की जाती है तो यह 8 स्लॉट्स से शुरू होती है । (देखें dictobject.h: 49 )

  • तालिका में प्रविष्टियां जोड़ते समय, हम कुछ स्लॉट से शुरू करते हैं i, जो कि कुंजी के हैश पर आधारित होता है। सीपीथॉन शुरू में उपयोग करता है i = hash(key) & mask(जहां mask = PyDictMINSIZE - 1, लेकिन यह वास्तव में महत्वपूर्ण नहीं है)। बस ध्यान दें कि प्रारंभिक स्लॉट, iकि जाँच की है कुंजी के हैश पर निर्भर करता है ।
  • यदि वह स्लॉट खाली है, तो प्रविष्टि को स्लॉट में जोड़ा जाता है (प्रविष्टि से मेरा मतलब है, <hash|key|value>)। लेकिन क्या होगा अगर उस स्लॉट पर कब्जा है! सबसे अधिक संभावना है क्योंकि एक और प्रविष्टि में एक ही हैश (हैश टक्कर!) है
  • यदि स्लॉट पर कब्जा है, तो CPython (और यहां तक ​​कि PyPy) हैश और कुंजी की तुलना करता है (तुलना से मेरा मतलब ==तुलना से नहीं is) हैश के खिलाफ स्लॉट में प्रविष्टि और वर्तमान प्रविष्टि की कुंजी डालने के लिए है ( तानाशाही विषय) : क्रमशः 337,344-345 )। यदि दोनों मेल खाते हैं, तो यह सोचता है कि प्रविष्टि पहले से मौजूद है, देता है और अगली प्रविष्टि में सम्मिलित होने के लिए आगे बढ़ता है। यदि या तो हैश या कुंजी मेल नहीं खाती है, तो यह जांच शुरू होती है ।
  • प्रोबिंग का अर्थ है कि यह एक खाली स्लॉट खोजने के लिए स्लॉट्स द्वारा स्लॉट्स को खोजता है। तकनीकी रूप से हम बस एक-एक करके जा सकते हैं, i+1, i+2, ...और पहले उपलब्ध एक का उपयोग कर सकते हैं (यह रैखिक जांच है)। लेकिन टिप्पणियों में खूबसूरती से समझाया गया कारणों के लिए ( डिक्टोबोबेक्ट देखें : 33-126 ), सीपीथॉन यादृच्छिक जांच का उपयोग करता है । यादृच्छिक जांच में, अगले स्लॉट को छद्म यादृच्छिक क्रम में चुना जाता है। प्रविष्टि को पहले खाली स्लॉट में जोड़ा जाता है। इस चर्चा के लिए, अगले स्लॉट को चुनने के लिए उपयोग किया जाने वाला वास्तविक एल्गोरिदम वास्तव में महत्वपूर्ण नहीं है (देखें प्रोब के लिए एल्गोरिथ्म के लिए तानाशाह): 33-126 )। महत्वपूर्ण यह है कि स्लॉट्स तब तक जांचे जाते हैं जब तक कि पहला खाली स्लॉट नहीं मिल जाता।
  • लुकअप के लिए भी यही बात होती है, बस शुरुआती स्लॉट i से शुरू होता है (जहां मैं कुंजी के हैश पर निर्भर करता हूं)। यदि हैश और कुंजी दोनों स्लॉट में प्रविष्टि से मेल नहीं खाते हैं, तो यह जांच शुरू करता है, जब तक कि यह एक मैच के साथ स्लॉट नहीं पाता। यदि सभी स्लॉट समाप्त हो जाते हैं, तो यह विफल हो जाता है।
  • बीटीडब्ल्यू, dictयदि दो-तिहाई भरा हुआ है, तो इसका आकार बदल दिया जाएगा। यह लुक्स को धीमा करने से बचाता है। (देखें तानाशाह। 64: 65 )

नोट: मैंने पायथन डिक्ट कार्यान्वयन पर अपने स्वयं के सवाल के जवाब में शोध किया कि कैसे एक तानाशाह में कई प्रविष्टियां समान हैश मान हो सकती हैं। मैंने यहां प्रतिक्रिया का थोड़ा संपादित संस्करण पोस्ट किया है क्योंकि सभी शोध इस प्रश्न के लिए बहुत प्रासंगिक हैं।


8
आपने कहा, जब हैश और कुंजी दोनों मिलते हैं, तो यह (सेशन डालें) देता है और आगे बढ़ता है। क्या इस मामले में मौजूदा प्रविष्टि को अधिलेखित नहीं करना है?
0xc0de

65

पायथन के अंतर्निहित शब्‍दों को कैसे लागू किया गया है?

यहाँ लघु पाठ्यक्रम है:

  • वे हैश टेबल हैं। (पायथन के कार्यान्वयन की बारीकियों के लिए नीचे देखें।)
  • पायथन 3.6 के रूप में एक नया लेआउट और एल्गोरिथ्म, उन्हें बनाता है
    • कुंजी प्रविष्टि द्वारा आदेश दिया गया, और
    • कम जगह लें,
    • वस्तुतः प्रदर्शन में कोई लागत नहीं।
  • एक और ऑप्टिमाइज़ेशन स्पेस को बचाता है जब डाइक शेयर कीज़ (विशेष मामलों में)।

आदेश दिया गया पहलू पाइथन 3.6 के रूप में अनौपचारिक है (अन्य कार्यान्वयनों को बनाए रखने का मौका देने के लिए), लेकिन पायनियर 3.7 में आधिकारिक

पाइथन के शब्दकोश हैश टेबल हैं

लंबे समय तक इसने ठीक इसी तरह काम किया। अजगर 8 खाली पंक्तियों का प्रचार करेगा और कुंजी-मूल्य जोड़ी को छड़ी करने के लिए निर्धारित करने के लिए हैश का उपयोग करेगा। उदाहरण के लिए, यदि कुंजी के लिए हैश 001 में समाप्त हो गया है, तो इसे 1 (यानी 2 वें) सूचकांक (उदाहरण के लिए नीचे दिया गया है) में चिपका दिया जाएगा।

   <hash>       <key>    <value>
     null        null    null
...010001    ffeb678c    633241c4 # addresses of the keys and values
     null        null    null
      ...         ...    ...

प्रत्येक पंक्ति 64 बिट आर्किटेक्चर पर 24 बाइट्स, 32 बिट पर 12 लेती है। (ध्यान दें कि कॉलम हेडर हमारे उद्देश्यों के लिए यहां केवल लेबल हैं - वे वास्तव में मेमोरी में मौजूद नहीं हैं।)

यदि हैश एक preexisting कुंजी के हैश के समान है, तो यह एक टक्कर है, और फिर यह एक अलग स्थान पर कुंजी-मूल्य जोड़ी को चिपकाएगा।

5 कुंजी-मान संग्रहीत होने के बाद, जब एक और कुंजी-मूल्य जोड़ी जोड़ते हैं, तो हैश टकराव की संभावना बहुत बड़ी है, इसलिए शब्दकोश आकार में दोगुना हो जाता है। 64 बिट प्रक्रिया में, आकार बदलने से पहले, हमारे पास 72 बाइट्स खाली हैं, और उसके बाद, हम 10 खाली पंक्तियों के कारण 240 बाइट बर्बाद कर रहे हैं।

इसमें काफी जगह है, लेकिन लुकअप समय काफी स्थिर है। कुंजी तुलना एल्गोरिथ्म हैश की गणना करना है, अपेक्षित स्थान पर जाएं, कुंजी की आईडी की तुलना करें - यदि वे समान वस्तु हैं, तो वे समान हैं। यदि नहीं तो हैश मानों की तुलना करें, यदि वे समान नहीं हैं, तो वे समान नहीं हैं। और फिर, हम अंत में समानता के लिए कुंजी की तुलना करते हैं, और यदि वे समान हैं, तो मान लौटाएं। समानता के लिए अंतिम तुलना काफी धीमी हो सकती है, लेकिन पहले के चेक आमतौर पर अंतिम तुलना को शॉर्टकट करते हैं, जिससे लुकअप बहुत जल्दी हो जाता है।

टकराव की चीजें धीमी हो जाती हैं, और एक हमलावर सैद्धांतिक रूप से हैश टकरावों का उपयोग सेवा हमले से इनकार करने के लिए कर सकता है, इसलिए हमने हैश फ़ंक्शन के आरंभीकरण को ऐसे यादृच्छिक कर दिया कि यह प्रत्येक नई पायथन प्रक्रिया के लिए अलग-अलग हैश की गणना करता है।

ऊपर वर्णित व्यर्थ स्थान ने हमें शब्दकोशों के कार्यान्वयन को संशोधित करने के लिए प्रेरित किया है, एक रोमांचक नई विशेषता के साथ जो अब प्रविष्टि द्वारा आदेश दिए गए हैं।

नई कॉम्पैक्ट हैश टेबल्स

हम प्रविष्टि के सूचकांक के लिए एक सरणी का प्रचार करके, इसके बजाय शुरू करते हैं।

चूँकि हमारी पहली की-वैल्यू जोड़ी दूसरे स्लॉट में जाती है, इसलिए हम इस तरह इंडेक्स करते हैं:

[null, 0, null, null, null, null, null, null]

और हमारी मेज सिर्फ सम्मिलन क्रम से आबाद हो जाती है:

   <hash>       <key>    <value>
...010001    ffeb678c    633241c4 
      ...         ...    ...

इसलिए जब हम एक कुंजी के लिए एक लुकअप करते हैं, तो हम उस स्थिति की जांच करने के लिए हैश का उपयोग करते हैं जो हम उम्मीद करते हैं (इस मामले में, हम सीधे सरणी के इंडेक्स 1 में जाते हैं), फिर हैश-टेबल में उस इंडेक्स पर जाएं (जैसे इंडेक्स 0 ), जांचें कि चाबियाँ समान हैं (पहले वर्णित समान एल्गोरिथ्म का उपयोग करके), और यदि ऐसा है, तो मान लौटाएं।

हम निरंतर लुकअप समय को बनाए रखते हैं, कुछ मामलों में मामूली गति के नुकसान के साथ और दूसरों में लाभ, अपसाइड के साथ कि हम पूर्व-मौजूदा कार्यान्वयन पर काफी जगह बचाते हैं और हम प्रविष्टि क्रम को बनाए रखते हैं। इंडेक्स ऐरे में केवल रिक्त स्थान व्यर्थ बाइट्स हैं।

रेमंड हेटिंगर ने 2012 के दिसंबर में अजगर-देव पर इसे पेश किया । यह अंत में पायथन 3.6 में सीपीथॉन में मिला । सम्मिलन द्वारा आदेश देना 3.6 के लिए एक कार्यान्वयन विवरण माना जाता था ताकि पायथन के अन्य कार्यान्वयन को पकड़ने का मौका मिल सके।

साझा की

अंतरिक्ष को बचाने के लिए एक और अनुकूलन एक कार्यान्वयन है जो चाबियाँ साझा करता है। इस प्रकार, निरर्थक शब्दकोशों के बजाय जो उस स्थान को पूरा करते हैं, हमारे पास ऐसे शब्दकोश हैं जो साझा कुंजी और कुंजियों के हैश का पुन: उपयोग करते हैं। आप इसे इस तरह से सोच सकते हैं:

     hash         key    dict_0    dict_1    dict_2...
...010001    ffeb678c    633241c4  fffad420  ...
      ...         ...    ...       ...       ...

64 बिट मशीन के लिए, यह 16 बाइट्स प्रति कुंजी प्रति अतिरिक्त शब्दकोश बचा सकता है।

कस्टम ऑब्जेक्ट्स और विकल्प के लिए साझा की गई

इन साझा-कुंजी डिकेट का उपयोग कस्टम ऑब्जेक्ट्स के लिए किया जाना है __dict__। इस व्यवहार को प्राप्त करने के लिए, मेरा मानना ​​है कि आपको __dict__अपनी अगली वस्तु को पलटने से पहले अपनी आबादी को खत्म करने की आवश्यकता है ( पीईपी 412 देखें )। इसका मतलब है कि आपको अपनी सभी विशेषताओं को __init__या में असाइन करना चाहिए __new__, अन्यथा आपको अपनी अंतरिक्ष बचत नहीं मिल सकती है।

हालाँकि, यदि आप अपने __init__निष्पादन के समय अपनी सभी विशेषताओं को जानते हैं, तो आप __slots__अपनी वस्तु के लिए भी प्रदान कर सकते हैं , और गारंटी जो कि __dict__बिल्कुल भी नहीं बनाई गई है (यदि माता-पिता में उपलब्ध नहीं है), या यहां तक ​​कि अनुमति दें __dict__लेकिन गारंटी दें कि आपकी पूर्वाभास विशेषताएँ हैं किसी भी तरह स्लॉट में संग्रहीत। अधिक जानकारी के लिए __slots__, मेरा जवाब यहां देखें

यह सभी देखें:


1
आपने "हम", और "पायथन के अन्य कार्यान्वयन को पकड़ने का मौका देने के लिए" - क्या इसका मतलब है कि आप "चीजों को जानते हैं" और यह एक स्थायी विशेषता बन सकती है? क्या युक्ति द्वारा कोई आदेश देने के लिए नकारात्मक पक्ष है?
toonarmycaptain

आदेश दिए जाने का नकारात्मक पक्ष यह है कि यदि dicts को आदेश दिए जाने की अपेक्षा की जाती है तो वे आसानी से एक बेहतर / तेज कार्यान्वयन पर स्विच नहीं कर सकते हैं जो आदेश नहीं दिया गया है। ऐसा लगता नहीं है कि हालांकि यह मामला होगा। मैं "चीजों को जानता हूं" क्योंकि मैं बहुत सारी बातचीत देखता हूं और कोर सदस्यों और अन्य लोगों द्वारा लिखी गई बहुत सारी चीजों को मुझसे बेहतर वास्तविक-विश्व प्रतिष्ठा के साथ पढ़ता हूं, इसलिए यहां तक ​​कि अगर मेरे पास तत्काल उपलब्ध स्रोत का हवाला नहीं है, तो मैं आमतौर पर जानता हूं मैं क्या बात कर रहा हूँ। लेकिन मुझे लगता है कि आप रेमंड हेटिंगर की एक बातचीत से उस बिंदु को प्राप्त कर सकते हैं।
हारून हॉल

1
आपने कुछ अस्पष्ट तरीके से समझाया कि सम्मिलन कैसे काम करता है ("यदि हैश उसी तरह एक preexisting कुंजी के हैश के रूप में समाप्त हो गया, ... तो यह कुंजी-मान जोड़ी को एक अलग स्थान पर चिपका देगा" - कोई?), लेकिन आपने समझाया नहीं? कैसे लुकअप और सदस्यता परीक्षण काम करते हैं। यह बिल्कुल स्पष्ट नहीं है कि हैश द्वारा स्थान का निर्धारण कैसे किया जाता है, लेकिन मुझे लगता है कि आकार हमेशा 2 की शक्ति है, और आप हैश के अंतिम कुछ बिट्स लेते हैं ...
एलेक्सी

@Alexey अंतिम लिंक जो मैं आपको प्रदान करता हूं, वह अच्छी तरह से एनोटेट किए गए तानाशाही कार्यान्वयन देता है - जहां आप फ़ंक्शन को ऐसा कर सकते हैं, जो वर्तमान में 969 लाइन पर है, जिसे कहा जाता है find_empty_slot: github.com/python/cpython/blob/master/Objects/dictobject.c # L969 - और लाइन 134 पर शुरू होने वाला कुछ गद्य है जो इसका वर्णन करता है।
हारून हॉल

46

पायथन डिक्शनर्स ओपन एड्रेसिंग ( सुंदर कोड के अंदर संदर्भ ) का उपयोग करते हैं

नायब! ओपन एड्रेसिंग , उर्फ बंद हैशिंग , जैसा कि विकिपीडिया में उल्लेखित है, इसके विपरीत ओपन हैशिंग के साथ भ्रमित नहीं होना चाहिए !

ओपन एड्रेसिंग का अर्थ है कि ताना सरणी स्लॉट्स का उपयोग करता है, और जब किसी वस्तु की प्राथमिक स्थिति को तानाशाही में लिया जाता है, तो वस्तु का स्थान उसी क्रम में एक अलग अनुक्रमणिका में "परबर्शन" स्कीम का उपयोग करते हुए मांगा जाता है, जहां वस्तु का हैश मान भाग होता है। ।


5
"इसके विपरीत खुले हैशिंग में भ्रमित न हों! (जिसे हम स्वीकृत उत्तर में देखते हैं)।" - मुझे यकीन नहीं है कि आपने जो लिखा था, उस समय कौन सा उत्तर स्वीकार किया गया था, या उस उत्तर में क्या कहा गया था - लेकिन यह संक्षिप्त टिप्पणी वर्तमान में स्वीकृत उत्तर के बारे में सही नहीं है और इसे हटा दिया जाएगा।
टोनी डेलरो
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.