Skip to content

Commit 81675cf

Browse files
committed
create thread-safe defaultdict.__getitem__ method
1 parent 25027cd commit 81675cf

4 files changed

Lines changed: 73 additions & 28 deletions

File tree

Doc/library/collections.rst

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -730,7 +730,7 @@ stack manipulations such as ``dup``, ``drop``, ``swap``, ``over``, ``pick``,
730730
defaultdict(default_factory, iterable, /, **kwargs)
731731
732732
Return a new dictionary-like object. :class:`defaultdict` is a subclass of the
733-
built-in :class:`dict` class. It overrides one method and adds one writable
733+
built-in :class:`dict` class. It defines two methods and adds one writable
734734
instance variable. The remaining functionality is the same as for the
735735
:class:`dict` class and is not documented here.
736736

@@ -740,8 +740,15 @@ stack manipulations such as ``dup``, ``drop``, ``swap``, ``over``, ``pick``,
740740
arguments.
741741

742742

743-
:class:`defaultdict` objects support the following method in addition to the
744-
standard :class:`dict` operations:
743+
:class:`defaultdict` defines the following methods:
744+
745+
.. method:: __getitem__(key, /)
746+
747+
Does exactly the same thing as :meth:`dict.__getitem__`, but in a more
748+
:term:`thread-safe` way. When :term:`free threading` is enabled, the
749+
defaultdict is locked while the key is being looked up and the
750+
:meth:`__missing__` method is being called, thus ensuring that only one
751+
default value is generated and inserted for each missing key.
745752

746753
.. method:: __missing__(key, /)
747754

@@ -755,28 +762,31 @@ stack manipulations such as ``dup``, ``drop``, ``swap``, ``over``, ``pick``,
755762
If calling :attr:`default_factory` raises an exception this exception is
756763
propagated unchanged.
757764

758-
This method is called by the :meth:`~object.__getitem__` method of the
759-
:class:`dict` class when the requested key is not found; whatever it
760-
returns or raises is then returned or raised by :meth:`~object.__getitem__`.
765+
This method is called by the :meth:`__getitem__` method when the requested
766+
key is not found; whatever it returns or raises is then returned or raised
767+
by :meth:`__getitem__`.
761768

762769
Note that :meth:`__missing__` is *not* called for any operations besides
763-
:meth:`~object.__getitem__`. This means that :meth:`~dict.get` will, like
764-
normal dictionaries, return ``None`` as a default rather than using
765-
:attr:`default_factory`.
770+
`self[key]`. This means that `self.get(key)` will, like normal dictionaries,
771+
return ``None`` as a default rather than using :attr:`default_factory`.
766772

767773

768774
:class:`defaultdict` objects support the following instance variable:
769775

770-
771776
.. attribute:: default_factory
772777

773778
This attribute is used by the :meth:`~defaultdict.__missing__` method;
774779
it is initialized from the first argument to the constructor, if present,
775780
or to ``None``, if absent.
776781

782+
777783
.. versionchanged:: 3.9
778-
Added merge (``|``) and update (``|=``) operators, specified in
779-
:pep:`584`.
784+
Added merge (``|``) and update (``|=``) operators, specified in
785+
:pep:`584`.
786+
787+
.. versionchanged:: 3.15
788+
Added the :meth:`__getitem__` method which is safe to use with
789+
:term:`free threading` enabled.
780790

781791

782792
:class:`defaultdict` Examples

Include/internal/pycore_dict.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,8 @@ PyAPI_FUNC(Py_ssize_t) _Py_dict_lookup(PyDictObject *mp, PyObject *key, Py_hash_
123123
extern Py_ssize_t _Py_dict_lookup_threadsafe(PyDictObject *mp, PyObject *key, Py_hash_t hash, PyObject **value_addr);
124124
extern Py_ssize_t _Py_dict_lookup_threadsafe_stackref(PyDictObject *mp, PyObject *key, Py_hash_t hash, _PyStackRef *value_addr);
125125

126+
extern void _Py_dict_unhashable_type(PyObject *op, PyObject *key);
127+
126128
extern int _PyDict_GetMethodStackRef(PyDictObject *dict, PyObject *name, _PyStackRef *method);
127129

128130
// Exported for external JIT support

Modules/_collectionsmodule.c

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2222,9 +2222,39 @@ typedef struct {
22222222

22232223
static PyType_Spec defdict_spec;
22242224

2225+
PyDoc_STRVAR(defdict_getitem_doc,
2226+
"__getitem__($self, key, /)\n--\n\n\
2227+
Return self[key]. Try to create the item if it doesn't exist, by calling\n\
2228+
self.__missing__(key).\
2229+
");
2230+
2231+
static PyObject *
2232+
defdict_subscript(PyObject *op, PyObject *key)
2233+
{
2234+
Py_ssize_t ix;
2235+
Py_hash_t hash;
2236+
PyObject *value;
2237+
2238+
hash = _PyObject_HashFast(key);
2239+
if (hash == -1) {
2240+
_Py_dict_unhashable_type(op, key);
2241+
return NULL;
2242+
}
2243+
Py_BEGIN_CRITICAL_SECTION(op);
2244+
ix = _Py_dict_lookup((PyDictObject *)op, key, hash, &value);
2245+
if (value != NULL) {
2246+
Py_INCREF(value);
2247+
} else if (ix != DKIX_ERROR) {
2248+
value = PyObject_CallMethodOneArg(op, &_Py_ID(__missing__), key);
2249+
}
2250+
Py_END_CRITICAL_SECTION();
2251+
return value;
2252+
}
2253+
22252254
PyDoc_STRVAR(defdict_missing_doc,
2226-
"__missing__(key) # Called by __getitem__ for missing key; pseudo-code:\n\
2227-
if self.default_factory is None: raise KeyError((key,))\n\
2255+
"__missing__($self, key, /)\n--\n\n\
2256+
# Called by __getitem__ for missing key. Equivalent to:\n\
2257+
if self.default_factory is None: raise KeyError(key)\n\
22282258
self[key] = value = self.default_factory()\n\
22292259
return value\n\
22302260
");
@@ -2326,6 +2356,8 @@ defdict_reduce(PyObject *op, PyObject *Py_UNUSED(dummy))
23262356
}
23272357

23282358
static PyMethodDef defdict_methods[] = {
2359+
{"__getitem__", defdict_subscript, METH_O|METH_COEXIST,
2360+
defdict_getitem_doc},
23292361
{"__missing__", defdict_missing, METH_O,
23302362
defdict_missing_doc},
23312363
{"copy", defdict_copy, METH_NOARGS,
@@ -2506,6 +2538,7 @@ static PyType_Slot defdict_slots[] = {
25062538
{Py_tp_init, defdict_init},
25072539
{Py_tp_alloc, PyType_GenericAlloc},
25082540
{Py_tp_free, PyObject_GC_Del},
2541+
{Py_mp_subscript, defdict_subscript},
25092542
{0, NULL},
25102543
};
25112544

Objects/dictobject.c

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2398,8 +2398,8 @@ PyDict_GetItem(PyObject *op, PyObject *key)
23982398
"PyDict_GetItemRef() or PyDict_GetItemWithError()");
23992399
}
24002400

2401-
static void
2402-
dict_unhashable_type(PyObject *op, PyObject *key)
2401+
void
2402+
_Py_dict_unhashable_type(PyObject *op, PyObject *key)
24032403
{
24042404
PyObject *exc = PyErr_GetRaisedException();
24052405
assert(exc != NULL);
@@ -2428,7 +2428,7 @@ _PyDict_LookupIndexAndValue(PyDictObject *mp, PyObject *key, PyObject **value)
24282428

24292429
Py_hash_t hash = _PyObject_HashFast(key);
24302430
if (hash == -1) {
2431-
dict_unhashable_type((PyObject*)mp, key);
2431+
_Py_dict_unhashable_type((PyObject*)mp, key);
24322432
return -1;
24332433
}
24342434

@@ -2532,7 +2532,7 @@ PyDict_GetItemRef(PyObject *op, PyObject *key, PyObject **result)
25322532

25332533
Py_hash_t hash = _PyObject_HashFast(key);
25342534
if (hash == -1) {
2535-
dict_unhashable_type(op, key);
2535+
_Py_dict_unhashable_type(op, key);
25362536
*result = NULL;
25372537
return -1;
25382538
}
@@ -2548,7 +2548,7 @@ _PyDict_GetItemRef_Unicode_LockHeld(PyDictObject *op, PyObject *key, PyObject **
25482548

25492549
Py_hash_t hash = _PyObject_HashFast(key);
25502550
if (hash == -1) {
2551-
dict_unhashable_type((PyObject*)op, key);
2551+
_Py_dict_unhashable_type((PyObject*)op, key);
25522552
*result = NULL;
25532553
return -1;
25542554
}
@@ -2586,7 +2586,7 @@ PyDict_GetItemWithError(PyObject *op, PyObject *key)
25862586
}
25872587
hash = _PyObject_HashFast(key);
25882588
if (hash == -1) {
2589-
dict_unhashable_type(op, key);
2589+
_Py_dict_unhashable_type(op, key);
25902590
return NULL;
25912591
}
25922592

@@ -2746,7 +2746,7 @@ setitem_take2_lock_held(PyDictObject *mp, PyObject *key, PyObject *value)
27462746
{
27472747
Py_hash_t hash = _PyObject_HashFast(key);
27482748
if (hash == -1) {
2749-
dict_unhashable_type((PyObject*)mp, key);
2749+
_Py_dict_unhashable_type((PyObject*)mp, key);
27502750
Py_DECREF(key);
27512751
Py_DECREF(value);
27522752
return -1;
@@ -2924,7 +2924,7 @@ PyDict_DelItem(PyObject *op, PyObject *key)
29242924
assert(key);
29252925
Py_hash_t hash = _PyObject_HashFast(key);
29262926
if (hash == -1) {
2927-
dict_unhashable_type(op, key);
2927+
_Py_dict_unhashable_type(op, key);
29282928
return -1;
29292929
}
29302930

@@ -3266,7 +3266,7 @@ pop_lock_held(PyObject *op, PyObject *key, PyObject **result)
32663266

32673267
Py_hash_t hash = _PyObject_HashFast(key);
32683268
if (hash == -1) {
3269-
dict_unhashable_type(op, key);
3269+
_Py_dict_unhashable_type(op, key);
32703270
if (result) {
32713271
*result = NULL;
32723272
}
@@ -3679,7 +3679,7 @@ dict_subscript(PyObject *self, PyObject *key)
36793679

36803680
hash = _PyObject_HashFast(key);
36813681
if (hash == -1) {
3682-
dict_unhashable_type(self, key);
3682+
_Py_dict_unhashable_type(self, key);
36833683
return NULL;
36843684
}
36853685
ix = _Py_dict_lookup_threadsafe(mp, key, hash, &value);
@@ -4650,7 +4650,7 @@ dict_get_impl(PyDictObject *self, PyObject *key, PyObject *default_value)
46504650

46514651
hash = _PyObject_HashFast(key);
46524652
if (hash == -1) {
4653-
dict_unhashable_type((PyObject*)self, key);
4653+
_Py_dict_unhashable_type((PyObject*)self, key);
46544654
return NULL;
46554655
}
46564656
ix = _Py_dict_lookup_threadsafe(self, key, hash, &val);
@@ -4687,7 +4687,7 @@ dict_setdefault_ref_lock_held(PyObject *d, PyObject *key, PyObject *default_valu
46874687

46884688
hash = _PyObject_HashFast(key);
46894689
if (hash == -1) {
4690-
dict_unhashable_type(d, key);
4690+
_Py_dict_unhashable_type(d, key);
46914691
if (result) {
46924692
*result = NULL;
46934693
}
@@ -5128,7 +5128,7 @@ dict_contains(PyObject *op, PyObject *key)
51285128
{
51295129
Py_hash_t hash = _PyObject_HashFast(key);
51305130
if (hash == -1) {
5131-
dict_unhashable_type(op, key);
5131+
_Py_dict_unhashable_type(op, key);
51325132
return -1;
51335133
}
51345134

@@ -7234,7 +7234,7 @@ _PyDict_SetItem_LockHeld(PyDictObject *dict, PyObject *name, PyObject *value)
72347234
if (value == NULL) {
72357235
Py_hash_t hash = _PyObject_HashFast(name);
72367236
if (hash == -1) {
7237-
dict_unhashable_type((PyObject*)dict, name);
7237+
_Py_dict_unhashable_type((PyObject*)dict, name);
72387238
return -1;
72397239
}
72407240
return _PyDict_DelItem_KnownHash_LockHeld((PyObject *)dict, name, hash);

0 commit comments

Comments
 (0)