14

It seems reasonable to believe that dict.pop operates atomically, since it raises KeyError if the specified key is missing and no default is provided, like so:

d.pop(k)

However, the documentation does not appear to specifically address that point, at least not in the section specifically documenting dict.pop.

This question occurred to me as I was reviewing an answer of mine which used this pattern:

if k in d: del d[k]

At the time, I was not thinking of the potential condition that a key may be present during the if, but not at the time of del. If dict.pop does indeed provide an atomic alternative, then I should note that in my answer.

Community
  • 1
  • 1
Mattie
  • 20,280
  • 7
  • 36
  • 54

2 Answers2

32

For the default type, dict.pop() is a C-function call, which means that it is executed with one bytecode evaluation. This makes that call atomic.

Python threads switch only when the bytecode evaluation loop lets them, so at bytecode boundaries. Some Python C functions do call back into Python code (think __dunder__ special method hooks), but the dict.pop() method does not, at least not for the default dict type.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • Way above my paygrade explanation-wise. Just one question: anyone got an idea of the situation in Jython? – mike rodent Jun 30 '13 at 09:53
  • See the [Jython Concurrency documentation](http://www.jython.org/jythonbook/en/1.0/Concurrency.html): *Jython implements dict and set by using Java’s ConcurrentHashMap. This means you can just use these standard Python types, and still get high performance concurrency. (They are also atomic like in CPython, as we will describe.)* – Martijn Pieters Jun 30 '13 at 10:04
  • More in that same chapter, listing atomic operations: *Modifying a dictionary in place (e.g. adding an item, or calling the clear method)* – Martijn Pieters Jun 30 '13 at 10:12
5

Actually dict.pop() is not atomic. For example, if you're using object as a dict's key Python have to call object's __hash__() implementation. But you can use dict.popitem() instead which is truly atomic.

renskiy
  • 1,330
  • 1
  • 13
  • 12
  • 2
    Your answer fundamentally contradicts the top and accepted answer. Do you still believe yours is correct? If so, why? Else, could you put a disclaimer or sth so people like me don't get confused? – Yatharth Agarwal Apr 12 '14 at 15:48
  • Not fundamentally. @martijn-pieters told about standard types (actually base types). But in common it's not good idea to hope that dict.pop() is always atomic. – renskiy Apr 18 '14 at 06:44
  • When you say _"if you're using object as a dict's key"_, what do you mean by object? I thought *everything* in Python was one? – Yatharth Agarwal Apr 19 '14 at 06:22
  • 2
    By this I mean instance of user defined class used as key of dictionary. – renskiy Apr 22 '14 at 09:17
  • 2
    When doing something like `d.pop(key, None)` only one thread gets the value, others get `None`, right? Even if both threads `hash(key)` only one will win the race to remove it from the dictionary. Like `d.pop(generate_key(), None)` might not be atomic because `generate_key()` is not atomic, but removing from dict is still atomic. – lumbric Jan 17 '17 at 11:13
  • 2
    @YatharthAgarwal Standards for an acceptable answer have changed. In 2019 I think we would say martijn-pieters did not answer the question, because the question doesn't mention default type. It is also implementation dependent. Never be intimidated by reputations. – andy256 Oct 31 '19 at 11:44