2017-06-13 10 views
6

जैसा कि बताया जा here,हैश() का उपयोग करते हुए __eq__ को कब कॉल किया जाता है?

कोड के नीचे,

class Person(object): 
    def __init__(self, name, ssn, address): 
     self.name = name 
     self.ssn = ssn 
     self.address = address 
    def __hash__(self): 
     print('in hash') 
     return hash(self.ssn) 
    def __eq__(self, other): 
     print('in eq') 
     return self.ssn == other.ssn 

bob = Person('bob', '1111-222-333', None) 

jim = Person('jim bo', '1111-222-333', 'sf bay area') 


dmv_appointments = {} 
print('calling hash') 
dmv_appointments[bob] = 'tomorrow' 
print('calling hash') 
print(dmv_appointments[jim]) 
print('calling hash again') 
print(dmv_appointments[bob]) 

आउटपुट:

calling hash 
in hash 
calling hash 
in hash 
in eq 
tomorrow 
calling hash again 
in hash 
tomorrow 

प्रश्न:

क्यों __eq__jim तक पहुंचने पर कॉल किया जाता है लेकिन bob पर नहीं?

+4

मेरा अनुमान है कि यह पहले हैश की जांच कर रहा है, फिर _identity_ (तेज़ होने), और केवल _then_ समानता। इस प्रकार, जब आप 'बॉब' देखते हैं तो यह देखने के लिए '__eq__'' की जांच नहीं करनी है कि यह सही प्रविष्टि है, लेकिन 'jim' के लिए यह करता है। –

+0

संबंधित प्रश्न: https: // stackoverflow।कॉम/क्यू/40 9 77986/1639625 एक टिप्पणी सी कार्यान्वयन में यह कैसे किया जाता है इसके बारे में कुछ कहता है। –

+0

@tobias_k: हाँ, यह एक सीपीथन (पायथन संदर्भ दुभाषिया) कार्यान्वयन विस्तार है [सीओ परत पर वस्तुओं की तुलना करने के लिए मानक विधि के रूप में 'PyObject_RichCompareBool' का उपयोग करके] (https://docs.python.org/3/c -api/object.html # c.PyObject_RichCompareBool)। समानता जांच के लिए उस फ़ंक्शन का उपयोग करना पाइथन स्तर पर 'वापसी x है y या x == y' के बराबर प्रभावी है। – ShadowRanger

उत्तर

8

लघु जवाब: एक शब्दकोश देखने पहले जब एक बाल्टी खोज, और है कि केवल अगर विफल रहता है, एक (और अधिक महंगी) समानता की जांच (x == y) किया जाता है एक (सस्ते) संदर्भ समानता की जांच (x is y) करता है।

परिदृश्य

__hash__ समारोह __eq__ आंतरिक रूप से फोन नहीं करता है। यह देखते हुए कि आप bob और jim बनाते हैं, ऐसी कोई विधि नहीं कहा जाता है।

अगला bob'tomorrow' के साथ सहयोग करें। शब्दकोश की बाल्टी में जानने के लिए, आपको bob स्टोर करना होगा, आप हैश की गणना करें। अब एक बार ऐसा करने के बाद हम bob (और सही बाल्टी में मूल्य) स्टोर करते हैं।

अगला हम jim प्राप्त करना चाहते हैं। जानने के लिए कि कौन सी बाल्टी jim रहती है, हम हैश की गणना करते हैं। इसके बाद हम बाल्टी में खोजना शुरू करते हैं। बाल्टी में bob होगा। हम पहले संदर्भ संख्या (jim is bob) निष्पादित करते हैं लेकिन यह विफल रहता है, इसलिए हम समानता जांच पर फ़ॉलबैक करते हैं। वह चेक सफल होता है, इसलिए हम bob: 'tomorrow' से संबंधित मान वापस कर देते हैं।

एक ही परिदृश्य होता है जब हम bob देखने के लिए चाहते हैं: हम , हैश गणना बाल्टी लाने। संदर्भ जांच पर bob is bob पर, और वह सफल होता है। तो हमें एक (शायद अधिक महंगा समानता जांच) की आवश्यकता नहीं है। हम बस 'tomorrow' मान वापस करते हैं।

संदर्भ चेकों

तथ्य यह है कि एक संदर्भ की जांच पहले से किया जाता है निम्नलिखित (अस्वस्थ) कोड के साथ सिद्ध किया जा सकता:

class Person(object): 
    def __init__(self, name, ssn, address): 
     self.name = name 
     self.ssn = ssn 
     self.address = address 
    def __hash__(self): 
     print('in hash') 
     return hash(self.ssn) 
    def __eq__(self, other): print('in eq') return False

यहाँ हम समानता के लिए हमेशा False लौट आते हैं।तो भी: एक अच्छा समानता संबंधकर्मकर्त्ता है:

>>> bob == bob 
in eq 
False 
>>> bob is bob 
True 

bob खुद के बराबर (यह वास्तव में नहीं अच्छे डिजाइन है, के बाद से एक शब्दकोश के लिए, यह एक अनुबंध है कि एक वस्तु ही समान हो जाता है नहीं है, सममित और संक्रमणीय)।

>>> dmv_appointments = {} 
>>> dmv_appointments[bob] = 'tomorrow' 
in hash 
>>> dmv_appointments[bob] 
in hash 
'tomorrow' 
+0

आम तौर पर यह आवश्यक नहीं है कि सभी ऑब्जेक्ट स्वयं के बराबर हों। 'फ्लोट ('नैन')' और 'दशमलव। डेसिमल ('नान') 'उदाहरण के लिए, और उन प्रकारों को पायथन के साथ नहीं आते हैं। यह * आवश्यक है कि '==' dict कुंजी के लिए समकक्ष संबंध है, इसलिए उन 'व्यक्ति' वस्तुओं में से एक का उपयोग करके एक कोर कुंजी के रूप में dict कुंजी के अनुबंध को तोड़ देता है। – user2357112

+0

@ user2357112: हाँ कुछ अपवाद हैं, क्योंकि आम तौर पर "भ्रष्ट" परिणाम के रूप में 'NaN' देखें। अलग-अलग गणनाओं से उत्पन्न दो 'नान' आमतौर पर बराबर नहीं होते हैं। लेकिन सामान्य प्रतिबिंब में, समरूपता और पारगमन एक ध्वनि समानता संबंध के गुण हैं। –

2

शीर्षक जवाब देने के लिए: फिर भी, अगर हम सहयोगी 'tomorrow' साथ bob, हम अभी भी मूल्य bob के साथ जुड़े लाने के लिए सक्षम हैं

__eq__ हैश का उपयोग कर बुलाया जाता है जब()?

कभी नहीं।


अन्य प्रश्न:

क्यों __eq__ जिम लेकिन बॉब पर नहीं पहुंचने से संबंधित बुलाया जाता है?

यह अधिक जटिल है। यह समझने के लिए कि आपको यह जानने की जरूरत है कि एक शब्दकोश कैसे कार्यान्वित किया जाता है। मान लिया जाये कि CPython यह एक hash स्तंभ, एक key स्तंभ और एक value स्तंभ युक्त एक मेज हो जाएगा:

hash  | key  | value 
----------------------------------------- 
    - |  - |  - 
----------------------------------------- 
    - |  - |  - 

यह एक निश्चित आकार होगा, लेकिन यह काफी बड़ा हर संभव hash मान होना चाहिए नहीं होगा, तो यह hash के आधार पर स्थिति की गणना करेगा। उदाहरण के लिए यदि आप bob जोड़ सकते हैं तो यह हो सकता है (स्ट्रिंग हैश कुछ सीपीथॉन संस्करणों में यादृच्छिक हैं, इसलिए वास्तविक परिणाम अलग-अलग होंगे) hash7475314405837642385। शब्दकोश मान लिया जाये कि 2 का एक वास्तविक आकार की है (वास्तव में यह बड़ा हो जाएगा, लेकिन यह है कि अनावश्यक रूप से इस सवाल का जवाब में अंतरिक्ष बर्बाद होता है) यह सिर्फ सापेक्ष लेता है, तो यह 7475314405837642385 % 2 == 1 में रख देगा:

hash  | key  | value 
----------------------------------------- 
    - |  - |  - 
----------------------------------------- 
747...385| bob | 'tomorrow' 

जब आप एक महत्वपूर्ण यह

  • हमेशा देखने
  • तो यह स्थिति की गणना करेगा की hash गणना को देखने के लिए चाहते हैं।
  • तो यह hash उस स्थिति
  • में सहेजे गए देखने के hash तुलना अगर hash तों बराबर तो यह देखने और PyObject_RichCompareBool के साथ सहेजा key तुलना करेंगे कर रहे हैं।यही कारण है कि इच्छा:
      पहचान के लिए
    • पहली जांच: lookup is key
    • कि अगर False है यह lookup == key

की जाँच करेगा तो मामले में आप देखने bob:

  • हैश: 7475314405837642385
  • po sition: 7475314405837642385 % 2 ->1
  • एक प्रवेश पाया, तो तुलना hash तों: 7475314405837642385 == 7475314405837642385
  • कि बराबर तो पहचान के लिए जाँच था: bob is bob ->True

तो यह 'tomorrow' रिटर्न एक समानता की जांच के बिना। दूसरे मामले में यह jim लिए जाँच करता है:

  • हैश: 7475314405837642385
  • स्थिति: 7475314405837642385 % 2 ->1
  • एक प्रवेश मिल गया है, तो hash तों तुलना: 7475314405837642385 == 7475314405837642385
  • कि बराबर तो के लिए जाँच था पहचान: jim is bob ->False
  • समानता के लिए चेक jim == bob ->True

तो यह 'tomorrow' देता है।


यह वास्तविक कार्यान्वयन का अनुमान है (इसमें कुछ विवरण गुम हैं)। hash ई बराबर या lookup is not key and lookup != key के बराबर नहीं हैं, लेकिन यह आपके द्वारा पूछे जाने वाले व्यवहार को समझने के लिए वास्तव में महत्वपूर्ण नहीं है, यह अधिक जटिल हो जाता है।

हालांकि, मुझे वास्तव में यह कहना है: आप जो कर रहे हैं वह वास्तव में खतरनाक है क्योंकि आपकी कक्षा अपरिवर्तनीय नहीं है। आप गलती से बचाया शब्दकोश प्रविष्टि आपके लिए अनुपलब्ध बना सकता है:

dmv_appointments = {bob: 1} 
bob.ssn = '1'  # changing ssn changes the hash! 
dmv_appointments[bob] 
--------------------------------------------------------------------------- 
KeyError         Traceback (most recent call last) 
<ipython-input-35-3920ada7bab1> in <module>() 
    15 dmv_appointments = {bob: 1} 
    16 bob.ssn = '1' 
---> 17 dmv_appointments[bob] 

KeyError: <__main__.Person object at 0x000001BD5DDCC470> 

(यह अभी भी मामले में काम कर सकता था नई hash "पुराने" हैश के बराबर है, लेकिन वह नहीं बल्कि आकस्मिक होगा)।

क्योंकि जब आप अपने उदाहरण के hash बदल है कि - क्योंकि यह मान लिया गया है शब्दकोश बचाया hash अपडेट नहीं करेंगे सभी कुंजियों अपरिवर्तनीय कर रहे हैं! तो शब्दकोश या तो यह मान लेगा कि यह किसी अन्य स्थिति में सहेजा जाएगा या यदि स्थिति (चमत्कारी रूप से) समान होगी तो यह उस चरण में विफल हो जाएगी जहां इसमें वास्तविक हैंश शामिल हैं।

संबंधित मुद्दे