python - ऑर्डर्ड डिक्ट के मूल्य बराबर क्यों नहीं हैं?



python-3.x dictionary ordereddictionary (4)

पायथन 3 में, dict.keys() और dict.values() विशेष पुनरावृत्त वर्गों को क्रमशः एक collections.abc.KeysView dict.values() और एक collections.abc.ValuesView dict.values() । पहले व्यक्ति को set से __eq__ विधि का वारिस __eq__ , दूसरा डिफ़ॉल्ट object.__eq__ का उपयोग करता है object.__eq__ जो ऑब्जेक्ट पहचान पर परीक्षण करता है।

पायथन 3 के साथ:

>>> from collections import OrderedDict
>>> d1 = OrderedDict([('foo', 'bar')])
>>> d2 = OrderedDict([('foo', 'bar')])

मैं समानता की जांच करना चाहता था:

>>> d1 == d2
True
>>> d1.keys() == d2.keys()
True

परंतु:

>>> d1.values() == d2.values()
False

क्या आप जानते हैं कि मूल्य बराबर क्यों नहीं हैं?

मैंने इसका परीक्षण पायथन 3.4 और 3.5 के साथ किया है।

इस सवाल के बाद, मैंने अतिरिक्त विवरण रखने के लिए पायथन-विचार मेलिंग सूची पर पोस्ट किया:

https://mail.python.org/pipermail/python-ideas/2015-December/037472.html


Python3 में, d1.values() और d2.values() collections.abc.ValuesView हैं d2.values() ऑब्जेक्ट्स:

>>> d1.values()
ValuesView(OrderedDict([('foo', 'bar')]))

उन्हें किसी ऑब्जेक्ट के रूप में तुलना न करें, सी उन्हें सूचियों पर घुमाएं और फिर उनकी तुलना करें:

>>> list(d1.values()) == list(d2.values())
True

जांच करना क्यों यह कुंजी की तुलना करने के लिए काम करता है, _collections_abc.py के _collections_abc.py में, KeysView Set से विरासत में है जबकि ValuesView नहीं करता है:

class KeysView(MappingView, Set):

class ValuesView(MappingView):
  • ValuesView और उसके माता-पिता में ValuesView लिए ट्रेसिंग:

    MappingView ==> Sized ==> ABCMeta ==> type ==> object

    __eq__ केवल object में लागू किया गया object और ओवरराइड नहीं किया गया है।

  • दूसरी तरफ, __eq__ सीधे Set से __eq__ करता है।


दुर्भाग्यवश, दोनों मौजूदा उत्तर इस बात का समाधान नहीं करते हैं कि यह क्यों किया जाता है लेकिन यह कैसे किया जाता है इस पर ध्यान केंद्रित किया जाता है। वह मेलिंग सूची चर्चा अद्भुत थी, इसलिए मैं चीजों को जोड़ दूंगा:

odict.keys / dict.keys और odict.items / dict.items :

  • odict.keys ( odict.keys उप-वर्ग ) collections.abc.Set . dict.keys (यह एक सेट-जैसी ऑब्जेक्ट) के अनुरूप होने के कारण तुलना का समर्थन करता है। यह इस तथ्य के कारण संभव है कि एक शब्दकोश (आदेश दिया गया या नहीं) के अंदर keys अद्वितीय और हर्षनीय होने की गारंटी दी जाती हैं।
  • odict.items ( odict.items उप-वर्ग ) dict.items के समान कारण के लिए तुलना का भी समर्थन करता है। itemsview को ऐसा करने की इजाजत है क्योंकि यह item में से एक है (विशेष रूप से, मूल्य का प्रतिनिधित्व करने वाला दूसरा तत्व) हैशबल नहीं है, विशिष्टता की गारंटी है, हालांकि ( keys अद्वितीय होने के कारण):

    >>> od = OrderedDict({'a': []})
    >>> set() & od.items()
    TypeErrorTraceback (most recent call last)
    <ipython-input-41-a5ec053d0eda> in <module>()
    ----> 1 set() & od.items()
    
    TypeError: unhashable type: 'list'
    

    इन दोनों दृश्यों के लिए keys , items , तुलना एक सरल कार्य का उपयोग करती है जिसे all_contained_in (सुंदर पठनीय) कहा जाता है जो वस्तुओं को __contain__ विधि का उपयोग करता है ताकि शामिल विचारों में तत्वों की सदस्यता की जांच हो सके।

अब, odict.values / dict.values बारे में:

  • जैसा कि देखा गया है, odict.values ( dict.values [shocker] का उप-वर्ग ) एक सेट-जैसी ऑब्जेक्ट की तरह तुलना नहीं करता है । ऐसा इसलिए है क्योंकि किसी valuesview के values को सेट के रूप में प्रदर्शित नहीं किया जा सकता है, कारण दो गुना हैं:

    1. सबसे महत्वपूर्ण बात यह है कि दृश्य में डुप्लिकेट हो सकते हैं जिन्हें हटाया नहीं जा सकता है।
    2. दृश्य में गैर-धब्बेदार वस्तुएं हो सकती हैं (जो, स्वयं पर, दृश्य को इस तरह के इलाज के रूप में पर्याप्त नहीं है)।

जैसा कि user2357112 और user2357112 द्वारा मेलिंग सूची में एक टिप्पणी में कहा गया है, odict.values / dict.values एक मल्टीसेट है, सेट का एक सामान्यीकरण जो इसके तत्वों के कई उदाहरणों को अनुमति देता है। तुलना करने की कोशिश करना अंतर्निहित डुप्लिकेशंस के कारण keys या items तुलना keys रूप में तुच्छ नहीं है, ऑर्डरिंग और तथ्य यह है कि आपको शायद उन मानों के अनुरूप कुंजी को ध्यान में रखना होगा। dict_values जो इस तरह दिखना चाहिए:

>>> {1:1, 2:1, 3:2}.values()
dict_values([1, 1, 2])
>>> {1:1, 2:1, 10:2}.values()
dict_values([1, 1, 2])

वास्तव में बराबर हो, भले ही कुंजी के अनुरूप मान समान नहीं हैं? शायद? शायद नहीं? यह किसी भी तरह से सीधे आगे नहीं है और अपरिहार्य भ्रम पैदा करेगा।

हालांकि यह मुद्दा यह है कि मेलिंग सूची पर @abarnett से एक और टिप्पणी के साथ, keys और items साथ तुलना करने के लिए यह तुच्छ नहीं है:

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


source प्रयोग करें, ल्यूक!

सीपीथॉन में, range(...).__contains__ (एक विधि रैपर) अंततः एक साधारण गणना के लिए प्रतिनिधि होगा जो जांचता है कि मूल्य संभवतः सीमा में हो सकता है या नहीं। यहां गति का कारण यह है कि हम श्रेणी वस्तु के प्रत्यक्ष पुनरावृत्ति के बजाय सीमाओं के बारे में गणितीय तर्क का उपयोग कर रहे हैं। उपयोग किए गए तर्क की व्याख्या करने के लिए:

  1. जांचें कि संख्या start और stop start बीच है, और
  2. जांचें कि स्ट्रैड वैल्यू हमारे नंबर पर "कदम" नहीं है।

उदाहरण के लिए, 994 range(4, 1000, 2) क्योंकि:

  1. 4 <= 994 < 1000 , और
  2. (994 - 4) % 2 == 0

पूर्ण सी कोड नीचे शामिल किया गया है, जो स्मृति प्रबंधन और संदर्भ गिनती विवरणों के कारण थोड़ा अधिक वर्बोज़ है, लेकिन बुनियादी विचार वहां है:

static int
range_contains_long(rangeobject *r, PyObject *ob)
{
    int cmp1, cmp2, cmp3;
    PyObject *tmp1 = NULL;
    PyObject *tmp2 = NULL;
    PyObject *zero = NULL;
    int result = -1;

    zero = PyLong_FromLong(0);
    if (zero == NULL) /* MemoryError in int(0) */
        goto end;

    /* Check if the value can possibly be in the range. */

    cmp1 = PyObject_RichCompareBool(r->step, zero, Py_GT);
    if (cmp1 == -1)
        goto end;
    if (cmp1 == 1) { /* positive steps: start <= ob < stop */
        cmp2 = PyObject_RichCompareBool(r->start, ob, Py_LE);
        cmp3 = PyObject_RichCompareBool(ob, r->stop, Py_LT);
    }
    else { /* negative steps: stop < ob <= start */
        cmp2 = PyObject_RichCompareBool(ob, r->start, Py_LE);
        cmp3 = PyObject_RichCompareBool(r->stop, ob, Py_LT);
    }

    if (cmp2 == -1 || cmp3 == -1) /* TypeError */
        goto end;
    if (cmp2 == 0 || cmp3 == 0) { /* ob outside of range */
        result = 0;
        goto end;
    }

    /* Check that the stride does not invalidate ob's membership. */
    tmp1 = PyNumber_Subtract(ob, r->start);
    if (tmp1 == NULL)
        goto end;
    tmp2 = PyNumber_Remainder(tmp1, r->step);
    if (tmp2 == NULL)
        goto end;
    /* result = ((int(ob) - start) % step) == 0 */
    result = PyObject_RichCompareBool(tmp2, zero, Py_EQ);
  end:
    Py_XDECREF(tmp1);
    Py_XDECREF(tmp2);
    Py_XDECREF(zero);
    return result;
}

static int
range_contains(rangeobject *r, PyObject *ob)
{
    if (PyLong_CheckExact(ob) || PyBool_Check(ob))
        return range_contains_long(r, ob);

    return (int)_PySequence_IterSearch((PyObject*)r, ob,
                                       PY_ITERSEARCH_CONTAINS);
}

विचार में "मांस" का उल्लेख लाइन में किया गया है :

/* result = ((int(ob) - start) % step) == 0 */ 

अंतिम नोट के रूप में - कोड स्निपेट के नीचे range_contains फ़ंक्शन देखें। यदि सटीक प्रकार की जांच विफल हो जाती है तो हम वर्णित चतुर एल्गोरिदम का उपयोग नहीं करते हैं, बल्कि _PySequence_IterSearch का उपयोग करके श्रेणी की गूंगा पुनरावृत्ति खोज पर वापस _PySequence_IterSearch ! आप इस व्यवहार को दुभाषिया में देख सकते हैं (मैं यहां v3.5.0 का उपयोग कर रहा हूं):

>>> x, r = 1000000000000000, range(1000000000000001)
>>> class MyInt(int):
...     pass
... 
>>> x_ = MyInt(x)
>>> x in r  # calculates immediately :) 
True
>>> x_ in r  # iterates for ages.. :( 
^\Quit (core dumped)




python python-3.x dictionary ordereddictionary