Original code copyright (C) 2009-2022 Rudolf Cardinal (

This file is part of cardinal_pythonlib.

Licensed under the Apache License, Version 2.0 (the “License”); you may not use this file except in compliance with the License. You may obtain a copy of the License at

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Dictionary manipulations.

class cardinal_pythonlib.dicts.CaseInsensitiveDict(*args, **kwargs)[source]

A case-insensitive dictionary, as per, with updates for Python 3 and type hinting.

See also

Test code:

from cardinal_pythonlib.dicts import CaseInsensitiveDict

d1 = CaseInsensitiveDict()  # d1 is now: {}
d2 = CaseInsensitiveDict({'A': 1, 'b': 2})  # d2 is now: {'a': 1, 'b': 2}
d3 = CaseInsensitiveDict(C=3, d=4)  # d3 is now: {'c': 3, 'd': 4}

d1.update({'E': 5, 'f': 6})  # d1 is now: {'e': 5, 'f': 6}
d1.update(G=7, h=8)  # d1 is now: {'e': 5, 'f': 6, 'g': 7, 'h': 8}
'H' in d1  # True
d1['I'] = 9  # None, and key 'i' added
del d1['I']  # None, and key 'i' deleted
d1.pop('H')  # 8
d1.get('E')  # 5
d1.get('Z')  # None
d1.setdefault('J', 10)  # 10, and key 'j' added
d1.update([('K', 11), ('L', 12)])
d1  # {'e': 5, 'f': 6, 'g': 7, 'j': 10, 'k': 11, 'l': 12}

Dictionary initialization.

  • Optional positional argument is mapping or iterable. If an iterable, its elements are iterables of length 2. (Note that passing None is different from not passing anything, hence the signature. The type of the first argument, if present, is Union[Mapping, Iterable[Tuple[Any, Any]]].)

  • Keyword arguments are key/value pairs.

get(key: Any, default: Any | None = None) Any[source]

If the key is in the dictionary, return the corresponding value; otherwise, return default, which defaults to None.

pop(key: Any, *args, **kwargs) Any[source]

Retrieves/returns the item and removes it. Takes a single optional argument, being the default to return if the key is not present (otherwise raises KeyError). Note that supplying a default of None is different to supplying no default.

setdefault(key: Any, default: Any | None = None) Any[source]

As per the Python docs:

If key is in the dictionary, return its value. If not, insert key with a value of default and return default. default defaults to None.

update(*args, **kwargs) None[source]

As per the Python docs:

Update the dictionary with the key/value pairs from other, overwriting existing keys. Return None.

update``accepts either another dictionary object or an iterable of key/value pairs (as tuples or other iterables of length two). If keyword arguments are specified, the dictionary is then updated with those key/value pairs: ``d.update(red=1, blue=2)`().

… so the type of the first argument, if present, is Union[Mapping, .Iterable[Tuple[Any, Any]]].

class cardinal_pythonlib.dicts.HashableDict[source]

A dictionary that can be hashed.


class cardinal_pythonlib.dicts.LazyButHonestDict[source]

A dictionary that provides alternatives to get() and setdefault(), namely lazyget() and lazysetdefault(), that only evaluate their arguments if they have to.


Compared to the StackOverflow version: no obvious need to have a default returning None, when we’re implementing this as a special function. In contrast, helpful to have *args/**kwargs options.

class cardinal_pythonlib.dicts.LazyDict[source]

A dictionary that only evaluates the argument to setdefault() or get() if it needs to.


The *args/**kwargs parts are useful, but we don’t want to have to name ‘thunk’ explicitly.

get(key: Hashable, thunk: Any | None = None, *args: Any, **kwargs: Any) Any[source]

Return the value for key if key is in the dictionary, else default.

setdefault(key: Hashable, thunk: Any | None = None, *args: Any, **kwargs: Any) Any[source]

Insert key with a value of default if key is not in the dictionary.

Return the value for key if key is in the dictionary, else default.

cardinal_pythonlib.dicts.delete_keys(d: Dict[Any, Any], keys_to_delete: List[Any], keys_to_keep: List[Any]) None[source]

Deletes keys from a dictionary, in place.

  • d – dictonary to modify

  • keys_to_delete – if any keys are present in this list, they are deleted…

  • keys_to_keep – … unless they are present in this list.

cardinal_pythonlib.dicts.dict_diff(d1: Dict[Any, Any], d2: Dict[Any, Any], deleted_value: Any | None = None) Dict[Any, Any][source]

Returns a representation of the changes that need to be made to d1 to create d2.

  • d1 – a dictionary

  • d2 – another dictionary

  • deleted_value – value to use for deleted keys; see below


a dictionary of the format {k: v} where the k/v pairs are key/value pairs that are absent from d1 and present in d2, or present in both but with different values (in which case the d2 value is shown). If a key k is present in d1 but absent in d2, the result dictionary has the entry {k: deleted_value}.

Return type:


cardinal_pythonlib.dicts.get_case_insensitive_dict_key(d: Dict, k: str) str | None[source]

Within the dictionary d, find a key that matches (in case-insensitive fashion) the key k, and return it (or None if there isn’t one).

cardinal_pythonlib.dicts.map_keys_to_values(keys: List[Any], d: Dict[Any, Any], default: Any | None = None, raise_if_missing: bool = False, omit_if_missing: bool = False) List[Any][source]

The d dictionary contains a key -> value mapping.

We start with a list of potential keys in keys, and return a list of corresponding values – substituting default if any are missing, or raising KeyError if raise_if_missing is true, or omitting the entry if omit_if_missing is true.

cardinal_pythonlib.dicts.merge_dicts(*dict_args: Dict) Dict[source]

Given any number of dicts, shallow-copy them and merge into a new dict. Precedence goes to key/value pairs in dicts that are later in the list.


cardinal_pythonlib.dicts.merge_two_dicts(x: Dict, y: Dict) Dict[source]

Given two dicts, merge them into a new dict as a shallow copy, e.g.

z = merge_two_dicts(x, y)

If you can guarantee Python 3.5, then a simpler syntax is:

z = {**x, **y}


cardinal_pythonlib.dicts.prefix_dict_keys(d: Dict[str, Any], prefix: str) Dict[str, Any][source]

Returns a dictionary that’s a copy of as d but with prefix prepended to its keys.

cardinal_pythonlib.dicts.rename_key(d: Dict[str, Any], old: str, new: str) None[source]

Rename a key in dictionary d from old to new, in place.

cardinal_pythonlib.dicts.rename_keys(d: Dict[str, Any], mapping: Dict[str, str]) Dict[str, Any][source]

Returns a copy of the dictionary d with its keys renamed according to mapping.

  • d – the starting dictionary

  • mapping – a dictionary of the format {old_key_name: new_key_name}


a new dictionary

Keys that are not in mapping are left unchanged. The input parameters are not modified.

cardinal_pythonlib.dicts.rename_keys_in_dict(d: Dict[str, Any], renames: Dict[str, str]) None[source]

Renames, IN PLACE, the keys in d according to the mapping in renames.

  • d – a dictionary to modify

  • renames – a dictionary of the format {old_key_name: new_key_name}


cardinal_pythonlib.dicts.reversedict(d: Dict[Any, Any]) Dict[Any, Any][source]

Takes a k -> v mapping and returns a v -> k mapping.

cardinal_pythonlib.dicts.set_null_values_in_dict(d: Dict[str, Any], null_literals: List[Any]) None[source]

Within d (in place), replace any values found in null_literals with None.