The implementation of OrderedDict.__delitem__ in Python 3.7 is as follows:
def __delitem__(self, key, dict_delitem=dict.__delitem__):
'od.__delitem__(y) <==> del od[y]'
# Deleting an existing item uses self.__map to find the link which gets
# removed by updating the links in the predecessor and successor nodes.
dict_delitem(self, key)
link = self.__map.pop(key)
link_prev = link.prev
link_next = link.next
link_prev.next = link_next
link_next.prev = link_prev
link.prev = None
link.next = None
This code does 3 things:
- Remove an item from the internal key-value dictionary.
- Remove a node from the dictionary holding linked list nodes.
- Delete an item from a doubly linked list.
Since the average case complexity of all the above operations is constant, the average case complexity of OrderedDict.__delitem__ is constant as well.
Do note however that the worst case complexity of deleting a key from a dictionary is O(n), so the same applies for ordered dictionaries as well.
dictionary - how is the time complexity of dict.clear() is O(1) in python? - Stack Overflow
python - Removing key/value pairs in list of dicts - Code Review Stack Exchange
How can I remove a key from a Python dictionary? - Stack Overflow
How to get any element in a dictionary without a key, O(1) time complexity - Page 3 - Scripting Support - Developer Forum | Roblox
Some rationale for why it is being claimed to be O(1):
The clear() method is actually just assigning the internal dictionary structures to new empty values (as can be seen in the source). The seemingly O(n) part is a result of decrementing reference counts, and other GC-related stuff. But this is purely a function of the GC approach that CPython uses (i.e. reference counting); you can envision different approaches that wouldn't require explicit cleanup like this, or where the cleanup would happen much later (or even be amortized away). Since ideally the time complexity of clear() shouldn't depend on the underlying GC approach, all GC-related parts are omitted, making it "O(1)". IMO this is mostly a definitional argument than anything else, but this is at least some justification.
I first thought that dict.clear just performed some reference decrease to let the garbage collector do the dirty non-O(1) work, but looking at the source code (thanks timgeb for providing the link) it doesn't seem to be that:
oldvalues = mp->ma_values;
if (oldvalues == empty_values)
return;
/* Empty the dict... */
dictkeys_incref(Py_EMPTY_KEYS);
mp->ma_keys = Py_EMPTY_KEYS;
mp->ma_values = empty_values;
mp->ma_used = 0;
mp->ma_version_tag = DICT_NEXT_VERSION();
/* ...then clear the keys and values */
if (oldvalues != NULL) {
n = oldkeys->dk_nentries;
for (i = 0; i < n; i++)
Py_CLEAR(oldvalues[i]);
What I see is that if the dictionary had values, then a loop is performed to decrease references those values and set the pointers to NULL. So seems to be O(n) not O(1) since it depends on the number of the values.
When you assign to a new dict like this d = {}, this is O(1), but the garbage collector must delete the old object when not referenced anymore. That may not be right when assigning, but that will happen, unless python quits abruptly.
To delete a key regardless of whether it is in the dictionary, use the two-argument form of dict.pop():
my_dict.pop('key', None)
This will return my_dict[key] if key exists in the dictionary, and None otherwise. If the second parameter is not specified (i.e. my_dict.pop('key')) and key does not exist, a KeyError is raised.
To delete a key that is guaranteed to exist, you can also use
del my_dict['key']
This will raise a KeyError if the key is not in the dictionary.
Specifically to answer "is there a one line way of doing this?"
if 'key' in my_dict: del my_dict['key']
...well, you asked ;-)
You should consider, though, that this way of deleting an object from a dict is not atomic—it is possible that 'key' may be in my_dict during the if statement, but may be deleted before del is executed, in which case del will fail with a KeyError. Given this, it would be safest to either use dict.pop or something along the lines of
try:
del my_dict['key']
except KeyError:
pass
which, of course, is definitely not a one-liner.