Issue 30322: PyObject_GetIter does not behave as documented on dict objects (original) (raw)

According to the docs (https://docs.python.org/3/c-api/object.html) the PyObject_GetIter method should be equivalent to the python call iter(<some_dict>), but, when given a dict, the PyObject_GetIter returns an iterator over key-value pairs whereas the iter() method returns an iterator over keys only.

I tripped over this when giving the <some_dict>.update() a dict-like object that does not inherit from the builtin dict and implements its own __iter__().

The update() method eventually reaches the following piece of code: https://hg.python.org/cpython/file/4243df51fe43/Objects/dictobject.c#l2383

it = PyObject_GetIter(seq2); ... item = PyIter_Next(it); ... fast = PySequence_Fast(item, ""); ... key = PySequence_Fast_GET_ITEM(fast, 0); value = PySequence_Fast_GET_ITEM(fast, 1);

displaying the difference in behaviour between PyObject_GetIter and iter(o).

This from the help on dict.update():

| update(...) | D.update([E, ]**F) -> None. Update D from dict/iterable E and F. | If E present and has a .keys() method, does: for k in E: D[k] = E[k] | If E present and lacks .keys() method, does: for (k, v) in E: D[k] = v | In either case, this is followed by: for k in F: D[k] = F[k]

Likewise in the source for collections.abc.MutableMapping:

def update(*args, **kwds):
    ''' D.update([E, ]**F) -> None.  Update D from mapping/iterable E and F.
        If E present and has a .keys() method, does:     for k in E: D[k] = E[k]
        If E present and lacks .keys() method, does:     for (k, v) in E: D[k] = v
        In either case, this is followed by: for k, v in F.items(): D[k] = v
    '''
    if not args:
        raise TypeError("descriptor 'update' of 'MutableMapping' object "
                        "needs an argument")
    self, *args = args
    if len(args) > 1:
        raise TypeError('update expected at most 1 arguments, got %d' %
                        len(args))
    if args:
        other = args[0]
        if isinstance(other, Mapping):
            for key in other:
                self[key] = other[key]
        elif hasattr(other, "keys"):
            for key in other.keys():
                self[key] = other[key]
        else:
            for key, value in other:
                self[key] = value
    for key, value in kwds.items():
        self[key] = value