-
-
Notifications
You must be signed in to change notification settings - Fork 33.2k
Description
Bug report
How to reproduce
- Make such a class:
class Object: def __iter__(self, /): return iter(()) def keys(self, /): return ['1', '2']
- Call
dict(Object())
. - Call
d = {}
and thend.update(Object())
.
Expected result
At the step 2 an empty dictionary is returned.
At the step 3 d
stays empty.
Actual result
Both steps 2 and 3 raise a TypeError 'Object' object is not subscriptable
.
CPython versions tested on:
3.10
3.11
Operating systems tested on:
Windows 21H2 (19044.1645)
Ubuntu 20.04.6 LTS
Docs of dict
state:
If a positional argument is given and it is a mapping object, a dictionary is created with the same key-value pairs as the mapping object. Otherwise, the positional argument must be an iterable object.
Unfortunately, there is no link to what is considered as a mapping object.
In typeshed both dict
and dict.update
accept SupportsKeysAndGetItem
, i.e., any object with attributes keys
and __getitem__
.
But the experiment above shows that only keys
is enough. While typeshed
is a bit too restrictive in the case for iterable (only iterables of 2-sized tuples are allowed, but dict
accepts any iterable of 2-sized iterables), I think just checking for keys
is not enough.
In the actual C code there is such comment:
Lines 39 to 43 in 2982bdb
/* PyDict_Merge updates/merges from a mapping object (an object that | |
supports PyMapping_Keys() and PyObject_GetItem()). If override is true, | |
the last occurrence of a key wins, else the first. The Python | |
dict.update(other) is equivalent to PyDict_Merge(dict, other, 1). | |
*/ |
Thus, it is intended to check the presence of two attributes.
The error is here:
Lines 3426 to 3441 in 2982bdb
/* Single-arg dict update; used by dict_update_common and operators. */ | |
static int | |
dict_update_arg(PyObject *self, PyObject *arg) | |
{ | |
if (PyDict_CheckExact(arg)) { | |
return PyDict_Merge(self, arg, 1); | |
} | |
int has_keys = PyObject_HasAttrWithError(arg, &_Py_ID(keys)); | |
if (has_keys < 0) { | |
return -1; | |
} | |
if (has_keys) { | |
return PyDict_Merge(self, arg, 1); | |
} | |
return PyDict_MergeFromSeq2(self, arg, 1); | |
} |
This code evaluates whether attribute keys
is present. If the answer is true, calls PyDict_Merge
, and calls PyDict_MergeFromSeq2
otherwise.
Linked PRs
- gh-116938: Clarify documentation of
dict
anddict.update
regarding the positional argument they accept #125213 - [3.13] gh-116938: Clarify documentation of
dict
anddict.update
regarding the positional argument they accept (GH-125213) #125336 - [3.12] gh-116938: Clarify documentation of
dict
anddict.update
regarding the positional argument they accept (GH-125213) #125337 - gh-116938: Fix
dict.update
docstring and remove erraneous full stop fromdict
documentation #125421 - [3.13] gh-116938: Fix
dict.update
docstring and remove erraneous full stop fromdict
documentation (GH-125421) #126150 - [3.12] gh-116938: Fix
dict.update
docstring and remove erraneous full stop fromdict
documentation (GH-125421) #126151