This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

Author christian.barcenas
Recipients christian.barcenas, docs@python
Date 2015-07-18.08:54:58
SpamBayes Score -1.0
Marked as misclassified Yes
Message-id <1437209700.05.0.411435282726.issue24659@psf.upfronthosting.co.za>
In-reply-to
Content
I noticed an inconsistency today between the dict() documentation vs. implementation.

The documentation for the dict() built-in [1] states that the function accepts an optional positional argument that is either a mapping object [2] or an iterable object [3]. 

Consider the following:

    import collections.abc
    
    class MyIterable(object):
        def __init__(self):
            self._data = [('one', 1), ('two', 2)]
            
        def __iter__(self):
            return iter(self._data)
    
    class MyIterableWithKeysMethod(MyIterable):
        def keys(self):
            return "And now for something completely different"
    
    class MyIterableWithKeysAttribute(MyIterable):
        keys = "It's just a flesh wound!"
    
    assert issubclass(MyIterable, collections.abc.Iterable)
    assert issubclass(MyIterableWithKeysMethod, collections.abc.Iterable)
    assert issubclass(MyIterableWithKeysAttribute, collections.abc.Iterable)
    assert not issubclass(MyIterable, collections.abc.Mapping)
    assert not issubclass(MyIterableWithKeysMethod, collections.abc.Mapping)
    assert not issubclass(MyIterableWithKeysAttribute, collections.abc.Mapping)
    
    # OK
    assert dict(MyIterable()) == {'one': 1, 'two': 2}

    # Traceback (most recent call last):
    #   File "<stdin>", line 1, in <module>
    # TypeError: 'MyIterableWithKeysMethod' object is not subscriptable
    assert dict(MyIterableWithKeysMethod()) == {'one': 1, 'two': 2}

    # Traceback (most recent call last):
    # File "<stdin>", line 1, in <module>
    # TypeError: attribute of type 'str' is not callable
    assert dict(MyIterableWithKeysAttribute()) == {'one': 1, 'two': 2}

The last two assertions should not fail, and it appears that the offending code can be found in Objects/dictobject.c's dict_update_common:

    else if (arg != NULL) {
        _Py_IDENTIFIER(keys);
        if (_PyObject_HasAttrId(arg, &PyId_keys))
            result = PyDict_Merge(self, arg, 1);
        else
            result = PyDict_MergeFromSeq2(self, arg, 1);
    }

PyDict_Merge is used to merge key-value pairs if the optional parameter is a mapping, and PyDict_MergeFromSeq2 is used if the parameter is an iterable.

My immediate thought was to substitute the _PyObject_HasAttrId call with PyMapping_Check which I believe would work in 2.7, but due to #5945 I don't think this fix would work in 3.x.

Thoughts?

[1] https://docs.python.org/3.6/library/stdtypes.html#dict
[2] https://docs.python.org/3.6/glossary.html#term-mapping
[3] https://docs.python.org/3.6/glossary.html#term-iterable
History
Date User Action Args
2015-07-18 08:55:00christian.barcenassetrecipients: + christian.barcenas, docs@python
2015-07-18 08:55:00christian.barcenassetmessageid: <1437209700.05.0.411435282726.issue24659@psf.upfronthosting.co.za>
2015-07-18 08:54:59christian.barcenaslinkissue24659 messages
2015-07-18 08:54:58christian.barcenascreate