python पाइथन में लैम्ब्डा फ़ंक्शन के लिए हैश




python-2.7 hash (4)

मैं एक लैम्ब्डा समारोह के हैश प्राप्त करने की कोशिश कर रहा हूं। मुझे दो मान क्यों मिलते हैं (8746164008739 और -9223363290690767077)? लैम्ब्डा समारोह से हैश हमेशा एक मूल्य क्यों नहीं है?

>>> fn = lambda: 1
>>> hash(fn)
-9223363290690767077
>>> fn = lambda: 1
>>> hash(fn)
8746164008739
>>> fn = lambda: 1
>>> hash(fn)
-9223363290690767077
>>> fn = lambda: 1
>>> hash(fn)
8746164008739
>>> fn = lambda: 1
>>> hash(fn)
-9223363290690767077

दो ऑब्जेक्ट्स को उसी मान पर हैश तक गारंटी नहीं दी जाती है जब तक वे समान [1] की तुलना नहीं करते।

पाइथन फ़ंक्शन (लैम्ब्डा समेत) बराबर तुलना नहीं करते हैं, भले ही उनके पास समान कोड हो [2]। उदाहरण के लिए:

>>> (lambda: 1) == (lambda: 1)
False

कार्यान्वयन के अनुसार, यह व्यवहार इस तथ्य के कारण है कि कार्य वस्तुएं अपना स्वयं का समानता ऑपरेटर प्रदान नहीं करती हैं। इसके बजाए, वे उस डिफ़ॉल्ट व्यक्ति का उत्तराधिकारी होते हैं जो ऑब्जेक्ट की पहचान का उपयोग करता है, यानी उसका पता। documentation :

यदि कोई __cmp__() , __eq__() या __ne__() ऑपरेशन परिभाषित नहीं किया गया है, तो क्लास इंस्टेंस की तुलना ऑब्जेक्ट पहचान ("पता") से की जाती है।

यहां आपके विशेष उदाहरण में क्या होता है:

fn = lambda: 1  # New function is allocated at address A and stored in fn.
fn = lambda: 1  # New function is allocated at address B and stored in fn.
                # The function at address A is garbage collected.
fn = lambda: 1  # New function is allocated at address A and stored in fn.
                # The function at address B is garbage collected.
fn = lambda: 1  # New function is allocated at address B and stored in fn.
                # The function at address A is garbage collected.
...

चूंकि पता A हमेशा एक मान पर धोया जाता है, और दूसरे को B को संबोधित करता है, तो आप दो मानों के बीच hash(fn) वैकल्पिक देख रहे हैं। यह वैकल्पिक व्यवहार, हालांकि, कार्यान्वयन आर्टेफैक्ट है और एक दिन बदल सकता है, उदाहरण के लिए, कचरा कलेक्टर को थोड़ा अलग व्यवहार करने के लिए बनाया गया था।

@Ruakh द्वारा निम्नलिखित अंतर्दृष्टि नोट का योगदान किया गया है:

यह ध्यान देने योग्य है कि यह निर्धारित करने के लिए कि क्या दो कार्य बराबर हैं, एक सामान्य प्रक्रिया लिखना संभव नहीं है। (यह undecidability की समस्या की undecidability का एक परिणाम है ।) इसके अलावा, दो पायथन फ़ंक्शन अलग-अलग व्यवहार कर सकते हैं भले ही उनका कोड समान हो (क्योंकि वे अलग-अलग-समान-नामित चरों का जिक्र कर सकते हैं)। तो यह समझ में आता है कि पायथन फ़ंक्शन समानता ऑपरेटर को अधिभारित नहीं करते हैं: डिफ़ॉल्ट ऑब्जेक्ट-पहचान तुलना से बेहतर कुछ भी लागू करने का कोई तरीका नहीं है।

[1] बातचीत आम तौर पर सच नहीं होती है: असमान की तुलना करने वाली दो वस्तुएं एक ही हैश मान हो सकती हैं। इसे हैश टकराव कहा जाता है।

[2] अपने भेड़ के बच्चे को बुलाकर और फिर परिणाम को हर्ष करना निश्चित रूप से वही मान देगा क्योंकि hash(1) हमेशा एक कार्यक्रम के भीतर समान होता है:

>>> (lambda: 1)() == (lambda: 1)()
True

यह तय करना कि क्या दो कार्य बराबर हैं, क्योंकि यह रोकथाम की समस्या का एक सुपरसेट है।

एक आदर्श दुनिया में, (और इसलिए हैशिंग) कार्यों की तुलना में एक प्रकार की त्रुटि होगी। पायथन स्पष्ट रूप से यह पसंद नहीं करता है, और इसके बजाय कार्यों की पहचान (और इसलिए हैश) की तुलना करने के लिए चुनता है।


lambda फ़ंक्शन ऑब्जेक्ट का हैश इसके मेमोरी एड्रेस पर आधारित है (सीपीथॉन में यह id फ़ंक्शन देता है)। इसका मतलब यह है कि किसी भी दो फ़ंक्शन ऑब्जेक्ट्स में अलग-अलग हैंश (मान लें कि कोई हैश टकराव नहीं है), भले ही फ़ंक्शंस में एक ही कोड हो।

प्रश्न में क्या हो रहा है, यह बताने के लिए, पहले ध्यान दें कि fn = lambda: 1 लिखना fn = lambda: 1 मेमोरी में एक नया फ़ंक्शन ऑब्जेक्ट बनाता है और नाम को fn से जोड़ता है। इसलिए इस नए समारोह में किसी भी मौजूदा कार्यों के लिए एक अलग हैश मान होगा।

fn = lambda: 1 दोहराएं fn = lambda: 1 , आपको हैश के लिए वैकल्पिक मान मिलते हैं क्योंकि जब fn नव निर्मित फ़ंक्शन ऑब्जेक्ट से बंधे होते हैं, तो फ़ंक्शन जो पहले इंगित करता है वह पाइथन द्वारा एकत्रित कचरा होता है। ऐसा इसलिए है क्योंकि इसमें अब कोई संदर्भ नहीं है (चूंकि नाम fn अब एक अलग वस्तु को इंगित करता है)।

पाइथन दुभाषिया फिर fn = lambda: 1 लिखकर बनाई गई अगली नई फ़ंक्शन ऑब्जेक्ट के लिए इस पुराने मेमोरी पते का पुन: उपयोग करता है।

यह व्यवहार विभिन्न प्रणालियों और पायथन कार्यान्वयन के बीच भिन्न हो सकता है।


प्रत्येक बार जब आप fn = lambda: 1 एक नया फ़ंक्शन ऑब्जेक्ट बनाया जाता है, और नाम fn से पुरानी पुरानी वस्तु को हटाने के लिए चिह्नित किया जाता है। लेकिन पाइथन ऑब्जेक्ट को ओएस पर वापस ले जाने के कारण ऑब्जेक्ट को डिलीकेट नहीं करता है। स्मृति आवंटन के लिए सिस्टम कॉल को कम करने और स्मृति विखंडन को कम करने के लिए पाइथन मेमोरी रीसायकल करने की कोशिश करता है जब यह कर सकता है। और इसलिए जब आप fn = lambda: 1 बनाते हैं fn = lambda: 1 एक बार तीसरा बार दुभाषिया नोटिस करता है कि उसे RAM की एक ब्लॉक मिल गई है जो कि नई फ़ंक्शन ऑब्जेक्ट के लिए काफी बड़ी है, और इसलिए यह उस ब्लॉक का उपयोग करता है। और इस प्रकार आपका तीसरा fn रैम के उस ब्लॉक में समाप्त होता है और इसलिए पहले fn के समान आईडी होती है, क्योंकि सीपीथॉन ऑब्जेक्ट्स की आईडी उनका स्मृति पता है।

(जैसा कि अन्य ने किसी ऑब्जेक्ट प्रकार के हैश का उल्लेख किया है जो __hash__ का विशिष्ट कार्यान्वयन प्रदान नहीं करता है, वह __hash__ में अपनी आईडी पर आधारित है। और यदि कोई वर्ग __cmp__ या __eq__ विधि को परिभाषित नहीं करता है तो उसे __hash__ ऑपरेशन को परिभाषित नहीं करना चाहिए) ।







lambda