0

Is there any way to access the non existing keys in OrderedDict with default values like which we do in defaultDict

Ex.
od = OrderedDict({'a':1})
print(od['b'])  # Output: KeyError: 'b'

This is the real problem KeyError is thrown in the following implementation

l =['a','b','c','b']
od = OrderedDict({})

for k in l:
    od[k] += 1 # KeyError

This implementation is intentionally avoided

for k in l:
    if k in od:
        od[k] += 1
    else:
        od[k] = 1
JanPo
  • 345
  • 2
  • 8
  • Does this answer your question? [How to implement an ordered, default dict?](https://stackoverflow.com/questions/6190331/how-to-implement-an-ordered-default-dict) – Arne Dec 30 '21 at 01:19
  • @Arne, the link provided is about implementation of a new custom Dict Type. I am trying to use features from library directly if there any features available in `OrderedDict` or if there any other Type available similar to `OrderedDict` in other libraries – JanPo Dec 30 '21 at 01:26
  • Is this for code that will have to run in Python <3.7 (or <3.6 in the default C implementation)? Because if not, [dictionaries now maintain insertion order](https://stackoverflow.com/a/39537308/12975140) even without using OrderedDict. – CrazyChucky Dec 30 '21 at 03:05
  • @CrazyChucky, It will be implemented in Python >= 3.7. Scenario is to do `dd['b'] += 1`. This will throw `KeyError` Exception if default `dd['b'] = 0` is not assigned before. – JanPo Jan 01 '22 at 08:31
  • 1
    @JanPo Then just use defaultdict. It maintains its insertion order. You don't need OrderedDict. – CrazyChucky Jan 01 '22 at 11:24
  • Thanks for the answer. The above comment is intended to use with `od` instead of `dd` Scenario is to do `od['b'] `+= 1. This will throw `KeyError` Exception if default `od['b']` = 0 is not assigned before. – JanPo Jan 01 '22 at 11:57
  • 2
    I understand that. I'm just saying that if the reason you need OrderedDict is to maintain key order, you don't actually need it at all, because defaultdict now does that all in its own (and so does plain ol' dict). Or is there another reason you need to use OrderedDict specifically? – CrazyChucky Jan 01 '22 at 12:07
  • 1
    In other words, if you explain why you need OrderedDict instead of defaultdict, we might be better able to help you. – CrazyChucky Jan 01 '22 at 15:53
  • Considering about compatibility with previous python versions. It would be nice if i could have additional information why do we need another additional `OrderedDict` data structure in `collection` if `deafaultDict` could do all sorting/ordering now. – JanPo Jan 02 '22 at 10:57
  • I've edited my answer to include why current Python has both. – CrazyChucky Jan 02 '22 at 13:31

3 Answers3

2

Just inherit from OrderedDict and redefine __getitem__:

from collections import OrderedDict


class OrderedDictFailFree(OrderedDict):
    def __getitem__(self, name):
        try:
            return OrderedDict.__getitem__(self, name)
        except KeyError:
            return None


od = OrderedDictFailFree({'a': 1})
print(od['b'])
Yevgeniy Kosmak
  • 3,561
  • 2
  • 10
  • 26
  • What if you also redefined `__init__` to accept the default value (or callable, to replicate the way defaultdict works)? – CrazyChucky Jan 01 '22 at 11:32
  • 2
    If you're implementing this by hand, the correct way is to define `__missing__` instead of overriding `__getitem__`. You can completely delete the definition of `__getitem__` and define `def __missing__(self, key): return None` and it will behave exactly the same as the above, except *significantly* more efficiently for lookups where the key already exists. – ShadowRanger Jan 01 '22 at 15:15
2

This is certainly doable, as Yevgeniy Kosmak's answer shows. However, keep in mind that in Python >=3.7 (or even >=3.6 as long as you're using the default CPython implementation), all dictionaries now maintain insertion order anyway. So in the event that you don't need compatibility with older versions, and the only thing you need is keys that maintain order, you may very well not need OrderedDict at all. defaultdict will maintain its order just as well.

There are still some differences between OrderedDict and dict/defaultdict, however, which may or may not affect your use case. These include:

  • Equality checks between two OrderedDicts check not only keys/values, but order as well. (This is not true of two defaultdicts, or an OrderedDict checked against a defaultdict.)
  • An OrderedDict's move_to_end() method can efficiently move a key/value pair to the front or end.
  • An OrderedDict's popitem() method can optionally pop and return the first key/value instead of the last. (dict's popitem() returns the last-inserted key/value—or, prior to 3.7, an arbitrary pair.)
  • Prior to 3.8, dict/defaultdict didn't directly support reverse iteration, such as with reversed(). However, in 3.6 or 3.7 you can still achieve this with an intermediary list or tuple.

Note: Even if dict now replicated all of OrderedDict's functionality, the latter would still probably not be removed from Python. Existing older programs already use OrderedDict, and Python usually tries not to break existing code with new minor version releases (e.g. 3.6 to 3.7) unless there's a compelling reason, like adding new keywords to the syntax. See here for more info on the Python versioning system, and here for details on backwards compatibility specifically.

CrazyChucky
  • 3,263
  • 4
  • 11
  • 25
  • 3. Someone might want the advantages that `OrderedDict` still has. – Kelly Bundy Jan 02 '22 at 15:48
  • 1
    Check out [its documentation](https://docs.python.org/3/library/collections.html#collections.OrderedDict). Note the two methods and their parameters, and the order-sensitive comparisons mentioned there. I think I've also used `OrderedDict` for its `popitem(False)` a while ago because it was much *faster* than doing the equivalent with a regular dict. – Kelly Bundy Jan 02 '22 at 16:49
  • @KellyBundy Thank you! I can't believe I missed that somehow. I have edited my answer, and learned new things to boot. – CrazyChucky Jan 02 '22 at 17:01
0

It seems we could use od.setdefault('b',0) although it's not that elegant solution.

od = OrderedDict({'a':1})
print(od.setdefault('b',0)) # od.get('b',0) could be used if intention is not to change od
l =['a','b','c','b','b']
od = OrderedDict({})

for k in l:   
    od[k] = od.setdefault(k,0) + 1 # set key to 0 if there in no key k and increment by 1

JanPo
  • 345
  • 2
  • 8
  • 2
    You can just do `od[k] = od.setdefault(k, 0) + 1` (or `od[k] = od.get(k, 0) + 1` with equivalent effect in this case); `setdefault` returns the existing value (or the newly set default value when it was previously unset) so it can be inlined. – ShadowRanger Jan 01 '22 at 15:23
  • That's good suggestion. I will edit accordingly – JanPo Jan 01 '22 at 15:25