52

I can use map to implement the case insensitive list search with Python.

a = ['xyz', 'wMa', 'Pma'];

b = map(string.lower, a)
if 'Xyz'.lower() in b:
    print 'yes'

How can I do the same thing with dictionary?

I tried the following code, but ap has the list of ['a','b','c'], not the case insensitive dictionary.

a = {'a':1, 'B':2, 'c':3}
ap = map(string.lower, a)
martineau
  • 119,623
  • 25
  • 170
  • 301
prosseek
  • 182,215
  • 215
  • 566
  • 871
  • Did you want solutions that explicitly used map --- that's how I read the question the first time. – Rizwan Kassim Jul 21 '10 at 08:40
  • 2
    See [PEP-455](https://www.python.org/dev/peps/pep-0455/): this is scheduled for standard library inclusion in Python 3.5 (as `collections.TransformDict`, provided the transform is `str.casefold` or similar) – Nick T Jan 12 '15 at 21:51
  • 2
    [PEP-455 was ultimately rejected.](https://www.python.org/dev/peps/pep-0455/#rejection) – John Y Jun 01 '17 at 15:12

6 Answers6

54

Note that making a dictionary case-insensitive, by whatever mean, may well lose information: for example, how would you "case-insensitivize" {'a': 23, 'A': 45}?! If all you care is where a key is in the dict or not (i.e., don't care about what value corresponds to it), then make a set instead -- i.e.

theset = set(k.lower() for k in thedict)

(in every version of Python, or {k.lower() for k in thedict} if you're happy with your code working only in Python 2.7 or later for the sake of some purely decorative syntax sugar;-), and check with if k.lower() in theset: ....

Or, you could make a wrapper class, e.g., maybe a read-only one...:

import collections

class CaseInsensitiveDict(collections.Mapping):
    def __init__(self, d):
        self._d = d
        self._s = dict((k.lower(), k) for k in d)
    def __contains__(self, k):
        return k.lower() in self._s
    def __len__(self):
        return len(self._s)
    def __iter__(self):
        return iter(self._s)
    def __getitem__(self, k):
        return self._d[self._s[k.lower()]]
    def actual_key_case(self, k):
        return self._s.get(k.lower())

This will keep (without actually altering the original dictionary, so all precise information can still be retrieve for it, if and when needed) an arbitrary one of possibly-multiple values for keys that "collapse" into a single key due to the case-insensitiveness, and offer all read-only methods of dictionaries (with string keys, only) plus an actual_key_case method returning the actual case mix used for any given string key (or None if no case-alteration of that given string key matches any key in the dictionary).

Jim Ferrans
  • 30,582
  • 12
  • 56
  • 83
Alex Martelli
  • 854,459
  • 170
  • 1,222
  • 1,395
  • Very nice - this solved a problem I was having where an API was doing a case-insensitive match on a requested field name, but returning the canonical field name, so I'd ask for 'email', but get 'Email'. This dict let me map that back to the field name I asked for. Result! – metadaddy Nov 22 '11 at 08:51
  • 2
    small typo in __getitem__() method. self._s instead of self_s. Apparently I can't make a 1 character edit in SO (must be >=6)!! – SteveJ May 07 '12 at 22:47
  • 1
    Not quite a drop-in replacement for a dictionary, see a full one at http://stackoverflow.com/a/27890005/99834 – sorin Jan 11 '15 at 17:32
29

Start using a real case insensitive dictionary via:

from requests.structures import CaseInsensitiveDict

Or if you want to see the code:

class CaseInsensitiveDict(dict):

    """Basic case insensitive dict with strings only keys."""

    proxy = {}

    def __init__(self, data):
        self.proxy = dict((k.lower(), k) for k in data)
        for k in data:
            self[k] = data[k]

    def __contains__(self, k):
        return k.lower() in self.proxy

    def __delitem__(self, k):
        key = self.proxy[k.lower()]
        super(CaseInsensitiveDict, self).__delitem__(key)
        del self.proxy[k.lower()]

    def __getitem__(self, k):
        key = self.proxy[k.lower()]
        return super(CaseInsensitiveDict, self).__getitem__(key)

    def get(self, k, default=None):
        return self[k] if k in self else default

    def __setitem__(self, k, v):
        super(CaseInsensitiveDict, self).__setitem__(k, v)
        self.proxy[k.lower()] = k
Jagoda Gorus
  • 329
  • 4
  • 18
sorin
  • 161,544
  • 178
  • 535
  • 806
  • 10
    It's actually in requests.structures now: http://docs.python-requests.org/en/v0.5.0/api/#structures – Gallaecio Mar 27 '15 at 09:33
  • Updated link: https://github.com/psf/requests/blob/main/requests/structures.py – stephentgrammer Jun 21 '22 at 17:42
  • Note that the latest implementation does NOT subclass from `dict`, but from `requests.compat.MutableMapping`, itself taken from `collections.abc.MutableMapping`. This doesn't seem a very good choice, as module `requests.compat` is said to be to ease the transition from Python2 to Python3 so will probably be "retired" some day. – mike rodent Jun 18 '23 at 17:24
26

Using dict comprehensions (Python2.7+)

a_lower = {k.lower():v for k,v in a.items()}

If your python is too old for dict comprehensions

a_lower = dict((k.lower(),v) for k,v in a.items())

then look up the value with the lowercase version of the key

value = a_lower[key.lower()]
John La Rooy
  • 295,403
  • 53
  • 369
  • 502
  • Will this work for a nested dictionary? example: `d = { 'name': {'firstName': 'swad', 'lastName': 'chan'}` – Swadhikar Aug 05 '18 at 04:38
  • @SwadhikarC, no. You'd need to recursively rewrite all the keys in the nested structure. In Python3, you should use `casefold` instead of `lower` to work better with unicode. – John La Rooy Aug 05 '18 at 22:42
6
dict(zip(map(string.lower,a.keys()),a.values()))

will do what you're looking for.

map(function,iterable) works over the iterable; and iterable of the dictionary is the list of keys.

a = {'a': 1, 'c': 3, 'B': 2}
for i in a:
 print a
# returns a c B

zip brings together the keys and values back into pairs, but as a series of tuples. dict converts the tuples back into a dict.

You could also do something like

def myfunc(t):
 return (string.lower(t[0]),t[1])

map(myfunc,a.items())
# returns [('a', 1), ('c', 3), ('b', 2)
dict(map(myfunc,a.items()))
# returns {'a': 1, 'c': 3, 'b': 2}

Or, even more fun...

dict(map(lambda (key, value):(string.lower(key),value),a.items()))
Rizwan Kassim
  • 7,931
  • 3
  • 24
  • 34
5

If you are not needing the lookup very often you can use this function without wasting space for other copy of dictionary. It is slow though as all keys must be checked against every time.

a = {'xyz':2, 'wMa':8, 'Pma':9}

## if you do not use many times and/or the dict is very big

def case_insensitive_key(a,k):
    k = k.lower()
    return [a[key] for key in a if key.lower() == k]

print 'yes' if case_insensitive_key(a,'Xyz') else 'no'
Tony Veijalainen
  • 5,447
  • 23
  • 31
2

Just wanted to add __setitem__, pop to Alex Martelli's Answer:

from collections import Mapping

class CaseInsensitiveDict(Mapping):
    def __init__(self, d):
        self._d = d
        self._s = dict((k.lower(), k) for k in d)
    def __contains__(self, k):
        return k.lower() in self._s
    def __len__(self):
        return len(self._s)
    def __iter__(self): 
        return iter(self._s)
    def __getitem__(self, k):
        return self._d[self._s[k.lower()]]
    def __setitem__(self, k, v):
        self._d[k] = v
        self._s[k.lower()] = k
    def pop(self, k):
        k0 = self._s.pop(k.lower())
        return self._d.pop(k0)
    def actual_key_case(self, k):
        return self._s.get(k.lower())
rabin utam
  • 13,930
  • 3
  • 20
  • 15