2016-03-06 8 views
12

मुझे इसमें कुछ ऑब्जेक्ट्स के साथ एक इटरेटर मिला है और मैं अद्वितीय यूज़र का संग्रह बनाना चाहता हूं जिसमें मैं केवल एक बार प्रत्येक उपयोगकर्ता को सूचीबद्ध करता हूं। तो थोड़ा के आसपास खेल रहे हैं मैं दोनों एक सूची है और एक dict के साथ इसे करने की कोशिश:'ऑब्जेक्ट इन ऑब्जेक्ट' 'ऑब्जेक्ट इन ऑब्जेक्ट' से अलग व्यवहार करता है?

>>> for m in ms: print m.to_user # let's first look what's inside ms 
... 
Pete Kramer 
Pete Kramer 
Pete Kramer 
>>> 
>>> uniqueUsers = [] # Create an empty list 
>>> for m in ms: 
...  if m.to_user not in uniqueUsers: 
...   uniqueUsers.append(m.to_user) 
... 
>>> uniqueUsers 
[Pete Kramer] # This is what I would expect 
>>> 
>>> uniqueUsers = {} # Now let's create a dict 
>>> for m in ms: 
...  if m.to_user not in uniqueUsers: 
...   uniqueUsers[m.to_user] = 1 
... 
>>> uniqueUsers 
{Pete Kramer: 1, Pete Kramer: 1, Pete Kramer: 1} 

तो मैं इसे एक सूची में dict परिवर्तित करके परीक्षण किया जब अगर बयान कर रही है, और कहा कि के रूप में मैं इसे करने के लिए उम्मीद करेंगे काम करता है:

>>> uniqueUsers = {} 
>>> for m in ms: 
...  if m.to_user not in list(uniqueUsers): 
...   uniqueUsers[m.to_user] = 1 
... 
>>> uniqueUsers 
{Pete Kramer: 1} 

और मैं uniqueUsers.keys() के खिलाफ परीक्षण द्वारा एक समान परिणाम प्राप्त कर सकते हैं।

बात यह है कि मुझे समझ में नहीं आता कि यह अंतर क्यों होता है। मैंने हमेशा सोचा कि यदि आप if object in dict करते हैं, तो यह बस डिक्ट्स कुंजी की एक सूची बनाता है और फिर परीक्षण करता है, लेकिन यह स्पष्ट रूप से मामला नहीं है।

क्या कोई बता सकता है कि object in dict आंतरिक रूप से कैसे काम करता है और यह object in list (जैसा कि मैं इसकी अपेक्षा करता हूं) के समान व्यवहार नहीं करता?

+2

@vaultah यह करना पड़ता है (अन्यथा आप एक unhashable लेखन त्रुटि मिलेगा), लेकिन कार्यान्वयन '__eq__' के कार्यान्वयन के साथ गठबंधन नहीं होने की संभावना है। – poke

+0

आपने 'to_user' और मुख्य वर्ग को कैसे कार्यान्वित किया? पाइथन शब्दकोश डुप्लिकेट ऑब्जेक्ट्स को संरक्षित नहीं करता है क्योंकि आपके पास समान '__hash__' मान है, लेकिन यदि आप प्रत्येक कक्षा में एक से अधिक उदाहरण बनाते हैं तो आपको विभिन्न हैश मान के साथ एक नई वस्तु मिल जाएगी। (इस बिंदु के कारण उनके पास समान प्रतिनिधित्व है), लेकिन परिणामस्वरूप आपका शब्दकोश प्रतिनिधित्व नहीं होगा क्योंकि वे एक ही तार हैं और इसलिए एक ही हैश मान है। – Kasramvd

+0

@poke आपने +1 के नीचे एक अच्छा उत्तर पोस्ट किया है। हालांकि, unhashable TypeError के बारे में आपकी टिप्पणी सही नहीं है, [जैसा कि इस उत्तर में दिखाया गया है] (http://stackoverflow.com/a/17445665/1431750)। – aneroid

उत्तर

16

क्या हो रहा है यह समझने के लिए, आपको यह समझना होगा कि in ऑपरेटर, membership test, विभिन्न प्रकारों के लिए व्यवहार करता है।

सूचियों के लिए, मूलभूत रूप से सूचीबद्ध सूचियों के कारण यह बहुत आसान है: ऑर्डर किए गए सरणी जो डुप्लिकेट की परवाह नहीं करते हैं। यहां सदस्यता परीक्षा का पालन करने का एकमात्र संभावित तरीका सूची में पुन: प्रयास करना है और प्रत्येक आइटम को समानता पर जांचना है। इस तरह कुछ:

# x in lst 
for item in lst: 
    if x == item: 
     return True 
return False 

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

चूंकि हैश मान निर्धारित करता है कि हैश तालिका में एक आइटम कहाँ रखा गया है, यह महत्वपूर्ण है कि ऑब्जेक्ट्स जो समान हैंश मान के समान हैं। तो निम्नलिखित निहितार्थ सत्य होना चाहिए: x == y => hash(x) == hash(y)। रिवर्स को सत्य होने की आवश्यकता नहीं है; यह पूरी तरह मान्य है कि अलग-अलग ऑब्जेक्ट्स समान हैश मान उत्पन्न करते हैं।

जब किसी शब्दकोश पर सदस्यता परीक्षण किया जाता है, तो शब्दकोश सबसे पहले हैश मान की तलाश करेगा। यदि यह इसे पा सकता है, तो यह सभी वस्तुओं पर समानता जांच करेगा; अगर यह हैश मान नहीं मिला है, तो यह मान लिया गया है कि यह एक अलग वस्तु है:

# x in dct 
h = hash(x) 
items = getItemsForHash(dct, h) 
for item in items: 
    if x == item: 
     return True 
# items is empty, or no match inside the loop 
return False 

जब से तुम वांछित परिणाम है जब एक सूची के खिलाफ एक सदस्यता परीक्षण, इसका मतलब है कि आपके वस्तु समानता तुलना लागू करता है का उपयोग कर पाने के (__eq__) सही ढंग से। लेकिन जब से तुम सही परिणाम जब एक शब्दकोश का उपयोग कर नहीं मिलता है, वहाँ एक __hash__ कार्यान्वयन समानता तुलना कार्यान्वयन के साथ समन्वय से बाहर है कि लगता है:

>>> class SomeType: 
     def __init__ (self, x): 
      self.x = x 
     def __eq__ (self, other): 
      return self.x == other.x 
     def __hash__ (self): 
      # bad hash implementation 
      return hash(id(self)) 

>>> l = [SomeType(1)] 
>>> d = { SomeType(1): 'x' } 
>>> x = SomeType(1) 
>>> x in l 
True 
>>> x in d 
False 

ध्यान दें कि अजगर 2 में नई शैली कक्षाओं के लिए (कक्षाएं जो object से प्राप्त होती हैं), यह "खराब हैश कार्यान्वयन" (जो ऑब्जेक्ट आईडी पर आधारित है) डिफ़ॉल्ट है। इसलिए जब आप अपना खुद का __hash__ फ़ंक्शन लागू नहीं करते हैं, तो यह अभी भी उस का उपयोग करता है।इसका अंततः मतलब है कि जब तक कि आपके __eq__ केवल एक पहचान जांच (डिफ़ॉल्ट) निष्पादित करता है, हैश फ़ंक्शन सिंक से बाहर हो जाएगा।

तो समाधान __hash__ को इस तरह से कार्यान्वित करना है कि यह __eq__ में उपयोग किए गए नियमों के साथ संरेखित हो। उदाहरण के लिए, यदि आप दो सदस्यों की तुलना self.x और self.y की तुलना करते हैं, तो आपको उन दो सदस्यों पर एक कंपाउंड हैश का उपयोग करना चाहिए।

तो एक वर्ग परिभाषित करता है: अगर यह परिवर्तनशील है कि आप एक वस्तु hashable नहीं करना चाहिए

class SomeType (object): 
    def __init__ (self, x, y): 
     self.x = x 
     self.y = y 

    def __eq__ (self, other): 
     return self.x == other.x and self.y == other.y 

    def __hash__ (self): 
     return hash((self.x, self.y)) 

नोट: ऐसा करने के लिए सबसे आसान तरीका है उन मूल्यों की एक टपल के हैश मान वापस जाने के लिए है म्यूटेबल ऑब्जेक्ट्स और __eq__() विधि लागू करता है, इसे __hash__() लागू नहीं करना चाहिए, क्योंकि हैशबल संग्रह के कार्यान्वयन के लिए एक कुंजी हैश मान अपरिवर्तनीय है (यदि ऑब्जेक्ट का हैश मान बदलता है, तो यह गलत हैश बाल्टी में होगा)।

+2

"और तकनीकी रूप से भी आवश्यक है क्योंकि केवल इतनी संख्याएं हैं" - पायथन में, केवल इतनी संख्याएं नहीं हैं। – immibis

+1

पायथन में '9 ** 100000' जैसे कुछ का मूल्यांकन करें, और फिर मुझे बताएं कि पाइथन की संख्या सीमित है। (मेमोरी सीमाओं को अनदेखा कर रहा है, क्योंकि ऑब्जेक्ट्स भी स्मृति द्वारा सीमित हैं) – immibis

+1

कम से कम संदर्भ दुभाषिया में, प्रत्येक पायथन ऑब्जेक्ट को एक अद्वितीय नंबर असाइन किया जा सकता है जिसे स्मृति पता कहा जाता है। – immibis

8

टीएल; डीआर: in परीक्षण सूची के लिए __eq__ पर कॉल करता है। डिक्ट्स के लिए, यह पहले __hash__ पर कॉल करता है और यदि हैश मैचों में है, तो __eq__ पर कॉल करें।

  1. in परीक्षण केवल सूचियों के लिए __eq__ पर कॉल करता है।
    • एक __eq__ के बिना, में सत्ता तुलना हमेशा False है।

      • पहले __hash__

          से वस्तु के हैश हो जाता है:
      • dicts लिए, आप एक सही ढंग से __hash__और__eq__ कार्यान्वित इसे सही ढंग सेमें वस्तुओं की तुलना करने में सक्षम होने की जरूरत है

      • बिना 0 -, नए शैली के वर्गों के लिए, मैं टी id() का उपयोग करता है जो सभी ऑब्जेक्ट्स के लिए अद्वितीय है और इसलिए किसी मौजूदा 0 से मेल नहीं खाता है जब तक यह समान ऑब्जेक्ट नहीं है।
      • और जैसा कि @poke एक टिप्पणी में कहा:

        अजगर 2 में, नई शैली वर्गों (object से इनहेरिट) वस्तु की __hash__ कार्यान्वयन जो id() पर आधारित है वारिस है, ताकि जहां कि से आता है।

    • तो हैश मैच, तो__eq__other साथ उस वस्तु के लिए कहा जाता है।

      • परिणाम तब __eq__ रिटर्न पर निर्भर करता है।
    • हैश नहीं मैच करता है, तो __eq__ बुलाया नहीं है।

तो in परीक्षण सूचियों के लिए और dicts के लिए __eq__ ... कॉल लेकिन dicts के लिए, __hash__ के बाद ही उससे मिलते-जुलते हैश देता है। और __hash__ नहीं है None वापस नहीं करता है, कोई त्रुटि नहीं फेंकता है और इसे "अप्राप्य" नहीं बनाता है। ... पायथन 2 में। to_user कक्षा को सही ढंग से दाएं कुंजी के रूप में उपयोग करने के लिए, आपको __hash__ method होना चाहिए जो __eq__ के साथ सिंक में सही ढंग से कार्यान्वित किया गया है।

विवरण:

के लिए चेक m.to_user not in uniqueUsers "सूची में वस्तु" सही ढंग से काम किया है क्योंकि आप शायद एक __eq__ विधि को लागू किया है, के रूप में @poke बताया। (और यह to_user रिटर्न एक वस्तु, न कोई स्ट्रिंग प्रकट होता है।)

एक ही जांच "शब्दकोष में वस्तु" के लिए काम नहीं करता है, क्योंकि या तो:
(क) उस वर्ग में __hash__ बुरी तरह @poke के रूप में, कार्यान्वित किया जाता है यह भी बताया।
(बी) या आपने __hash__ बिल्कुल लागू नहीं किया है। यह Python2 नई शैली कक्षाओं में एक त्रुटि नहीं बढ़ाता है।

एक प्रारंभिक बिंदु के रूप the class in this answer का उपयोग करना:

>>> class Test2(object): 
...  def __init__(self, name): 
...   self.name = name 
... 
...  def __eq__(self, other): 
...   return self.name == other.name 
... 
>>> test_Dict = {} 
>>> test_List = [] 
>>> 
>>> obj1 = Test2('a') 
>>> obj2 = Test2('a') 
>>> 
>>> test_Dict[obj1] = 'x' 
>>> test_Dict[obj2] = 'y' 
>>> 
>>> test_List.append(obj1) 
>>> test_List.append(obj2) 
>>> 
>>> test_Dict 
{<__main__.Test2 object at 0x0000000002EFC518>: 'x', <__main__.Test2 object at 0x0000000002EFC940>: 'y'} 
>>> test_List 
[<__main__.Test2 object at 0x0000000002EFC518>, <__main__.Test2 object at 0x0000000002EFC940>] 
>>> 
>>> Test2('a') in test_Dict 
False 
>>> Test2('a') in test_List 
True 
+2

आपका टीएल; डॉ एक छोटी गलती है: '__eq__' को वास्तव में एक शब्दकोश में तत्व ढूंढने के लिए बुलाया जाता है, लेकिन केवल ऑब्जेक्ट के हैश का मूल्यांकन करने और हैश मैच ढूंढने के बाद। – poke

+0

संदेह है कि। और, अगर '__eq__' परिभाषित नहीं किया गया है लेकिन' __hash__' है, तो 'इन' परीक्षण अभी भी डिक्ट्स के लिए विफल रहता है। यह दोनों की जरूरत है। बेशक, सूची केवल '__eq__' का उपयोग करती है, इसके बिना, यह हमेशा झूठी होती है। – aneroid

+0

हाँ, हैश मान केवल उस स्थान को खोजने के लिए शब्दकोशों में पहले चरण के रूप में उपयोग किया जाता है जहां तत्व हैश तालिका में जाता है। शब्दकोश अब भी सुनिश्चित करने के लिए पाये जाने वाले सभी तत्वों पर एक समानता जांच का उपयोग करेगा। और अगर अतिसंवेदनशील नहीं है, तो '__eq__' पहचान पहचान पर वापस आ जाएगा। – poke

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