不区分大小写的字典


78

我希望我的字典不区分大小写。

我有以下示例代码:

text = "practice changing the color"

words = {'color': 'colour',
        'practice': 'practise'}

def replace(words,text):

    keys = words.keys()

    for i in keys:
        text= text.replace(i ,words[i])
    return  text

text = replace(words,text)

print text

输出=练习更改颜色

我想要另一个字符串,"practice changing the Color"Color以大写字母开头)也给出相同的输出。

我相信有一种使用转换为小写字母的通用方法, mydictionary[key.lower()]但是我不确定如何将其最好地集成到我的现有代码中。(如果无论如何这将是一种合理,简单的方法)。


4
请参阅PEP-455:已计划将其包含在Python 3.5中的标准库中(如collections.TransformDict提供转换一样,则为str.casefold
Nick T

Answers:


43

如果我对您的理解正确,并且希望以一种不区分大小写的方式键入字典的键,则一种方法是将dict子类化并重载setter / getter:

class CaseInsensitiveDict(dict):
    def __setitem__(self, key, value):
        super(CaseInsensitiveDict, self).__setitem__(key.lower(), value)

    def __getitem__(self, key):
        return super(CaseInsensitiveDict, self).__getitem__(key.lower())

1
是否也没有称为“ in”的特殊内置函数?
2010年

26
这是可能需要重载的方法的完整列表:setitemgetitemcontains,get,has_key,pop,setdefault和update。 init和fromkeys也可能应该被重载以确保正确初始化字典。也许我错了,在某个地方,Python承诺可以通过getitemsetitemcontains的方式实现get,hash_key,pop,setdefault,update和init的实现,但是我认为不是。
2010年

4
__contains__, get, and has_key自从我结束对它们的编码后,它添加到了答案中:)
Michael Merchant

7
该解决方案非常有限,因为它不适用于的许多常用用法 dict不要在代码中使用它-除了最简单的用法,它会破坏所有方法。显然,@ MichaelMerchant尝试添加缺少的内容,但适度拒绝了更改(我发生了同样的事情)。我添加了一个新的答案,可以在这里作为dict替代品使用。
m000 2015年


68

目前已批准的答案不适合工作,很多情况下,所以它不能被用来作为一个下拉dict更换。获得适当dict替换的一些技巧:

  • 重载涉及键的所有方法
  • 正确处理非字符串键
  • 正确处理类的构造函数

以下应该更好地工作:

class CaseInsensitiveDict(dict):
    @classmethod
    def _k(cls, key):
        return key.lower() if isinstance(key, basestring) else key

    def __init__(self, *args, **kwargs):
        super(CaseInsensitiveDict, self).__init__(*args, **kwargs)
        self._convert_keys()
    def __getitem__(self, key):
        return super(CaseInsensitiveDict, self).__getitem__(self.__class__._k(key))
    def __setitem__(self, key, value):
        super(CaseInsensitiveDict, self).__setitem__(self.__class__._k(key), value)
    def __delitem__(self, key):
        return super(CaseInsensitiveDict, self).__delitem__(self.__class__._k(key))
    def __contains__(self, key):
        return super(CaseInsensitiveDict, self).__contains__(self.__class__._k(key))
    def has_key(self, key):
        return super(CaseInsensitiveDict, self).has_key(self.__class__._k(key))
    def pop(self, key, *args, **kwargs):
        return super(CaseInsensitiveDict, self).pop(self.__class__._k(key), *args, **kwargs)
    def get(self, key, *args, **kwargs):
        return super(CaseInsensitiveDict, self).get(self.__class__._k(key), *args, **kwargs)
    def setdefault(self, key, *args, **kwargs):
        return super(CaseInsensitiveDict, self).setdefault(self.__class__._k(key), *args, **kwargs)
    def update(self, E={}, **F):
        super(CaseInsensitiveDict, self).update(self.__class__(E))
        super(CaseInsensitiveDict, self).update(self.__class__(**F))
    def _convert_keys(self):
        for k in list(self.keys()):
            v = super(CaseInsensitiveDict, self).pop(k)
            self.__setitem__(k, v)

3
很好,但是有一个小问题。updateis的超定义update(self, E=None, **F),意思E是可选的。您已经重新定义它为E必填项。添加,=None这将是完美的。
尼克·威廉姆斯

18
他们说,Python很简单。他们说,Python很有趣。
rr- 2015年

2
@ rr-。是完全公平的,想象的发言权C.这样做
疯狂的物理学家

1
Nitpick,但这对Unicode规范化没有适当的支持。
疯狂物理学家

6
在python 3中,抽象类型basestring被删除。str可以用作替代品。
Jan Schatz '18

55

10
from requests.structures import CaseInsensitiveDict
JimB

6
那可能行得通,但是如果您只需要一个不区分大小写的Dict,那么将请求添加为依赖项是很愚蠢的。
圣地亚哥巴苏尔

2
@santiagobasulto-直到(需要上班时间/截止日期截止)比率达到无穷大为止,这是“愚蠢的”
qneill

15

在我的特定情况下,我需要不区分大小写的查找,但是,我不想修改密钥的原始大小写。例如:

>>> d = {}
>>> d['MyConfig'] = 'value'
>>> d['myconfig'] = 'new_value'
>>> d
{'MyConfig': 'new_value'}

您可以看到字典仍然具有原始密钥,但是不区分大小写即可访问它。这是一个简单的解决方案:

class CaseInsensitiveKey(object):
    def __init__(self, key):
        self.key = key
    def __hash__(self):
        return hash(self.key.lower())
    def __eq__(self, other):
        return self.key.lower() == other.key.lower()
    def __str__(self):
        return self.key

获取和设置字典中的条目都需要__hash__和__eq__覆盖。如果密钥不区分大小写,这将创建散列到字典中相同位置的键。

现在,要么创建一个自定义词典,然后使用提供的密钥初始化CaseInsensitiveKey:

class CaseInsensitiveDict(dict):
    def __setitem__(self, key, value):
        key = CaseInsensitiveKey(key)
        super(CaseInsensitiveDict, self).__setitem__(key, value)
    def __getitem__(self, key):
        key = CaseInsensitiveKey(key)
        return super(CaseInsensitiveDict, self).__getitem__(key)

或者只需确保在使用字典时始终将CaseInsensitiveKey的实例作为键传递。


很好,谢谢!:)(请注意,此类未实现不区分大小写的“ dict(iterable)”构造函数,因此,如果需要,则必须添加它)
Joril

2
您应该使用.casefold()而不是.lower()进行比较,self.key.casefold() == other.key.casefold()以允许"ß""ss"等同于true等。
AJNeufeld '19

10

您是否考虑string.lower()在输入中使用并使用完全小写的字典?这有点棘手,但是可以用


这有点骇人听闻,但我认为这与Kim追求的目标一致。
约翰Y

这不是hacky。实际上,与覆盖字典类相比,这是不那么容易出错的方法。
Saher Ahwal

4
除非您想在首次设置密钥时保留原始大小写,否则这非常有用。
Daniel Roethlisberger

4

我已经通过pleasemorebacon修改了简单但不错的解决方案(谢谢!),使其稍微更紧凑,自包含,并进行了较小的更新,以允许从协议中构建{'a':1, 'B':2}并支持__contains__协议。最后,由于CaseInsensitiveDict.Key预计将字符串(还有什么可以区分大小写或没有),这是一个好主意,派生Key从类str的话,那是可能的,例如,转储CaseInsensitiveDictjson.dumps开箱。

# caseinsensitivedict.py
class CaseInsensitiveDict(dict):

    class Key(str):
        def __init__(self, key):
            str.__init__(key)
        def __hash__(self):
            return hash(self.lower())
        def __eq__(self, other):
            return self.lower() == other.lower()

    def __init__(self, data=None):
        super(CaseInsensitiveDict, self).__init__()
        if data is None:
            data = {}
        for key, val in data.items():
            self[key] = val
    def __contains__(self, key):
        key = self.Key(key)
        return super(CaseInsensitiveDict, self).__contains__(key)
    def __setitem__(self, key, value):
        key = self.Key(key)
        super(CaseInsensitiveDict, self).__setitem__(key, value)
    def __getitem__(self, key):
        key = self.Key(key)
        return super(CaseInsensitiveDict, self).__getitem__(key)

对于那些喜欢检查实际情况的人,这是一个基本的测试脚本:

# test_CaseInsensitiveDict.py
import json
import unittest
from caseinsensitivedict import *

class Key(unittest.TestCase):
    def setUp(self):
        self.Key = CaseInsensitiveDict.Key
        self.lower = self.Key('a')
        self.upper = self.Key('A')

    def test_eq(self):
        self.assertEqual(self.lower, self.upper)

    def test_hash(self):
        self.assertEqual(hash(self.lower), hash(self.upper))

    def test_str(self):
        self.assertEqual(str(self.lower), 'a')
        self.assertEqual(str(self.upper), 'A')

class Dict(unittest.TestCase):
    def setUp(self):
        self.Dict = CaseInsensitiveDict
        self.d1 = self.Dict()
        self.d2 = self.Dict()
        self.d1['a'] = 1
        self.d1['B'] = 2
        self.d2['A'] = 1
        self.d2['b'] = 2

    def test_contains(self):
        self.assertIn('B', self.d1)
        d = self.Dict({'a':1, 'B':2})
        self.assertIn('b', d)

    def test_init(self):
        d = self.Dict()
        self.assertFalse(d)
        d = self.Dict({'a':1, 'B':2})
        self.assertTrue(d)

    def test_items(self):
        self.assertDictEqual(self.d1, self.d2)
        self.assertEqual(
            [v for v in self.d1.items()],
            [v for v in self.d2.items()])

    def test_json_dumps(self):
        s = json.dumps(self.d1)
        self.assertIn('a', s)
        self.assertIn('B', s)

    def test_keys(self):
        self.assertEqual(self.d1.keys(), self.d2.keys())

    def test_values(self):
        self.assertEqual(
            [v for v in self.d1.values()],
            [v for v in self.d2.values()])

1
您应该使用.casefold()而不是.lower()进行比较,self.casefold() == other.key.casefold()并使用和hash(self.casefold())来使“ß”和“ ss”等价为true等。
AJNeufeld

3

尽管不区分大小写的字典是一种解决方案,并且有实现该问题的方法的答案,但在这种情况下,可能有一种更简单的方法。不区分大小写的搜索就足够了:

import re

text = "Practice changing the Color"
words = {'color': 'colour', 'practice': 'practise'}

def replace(words,text):
        keys = words.keys()
        for i in keys:
                exp = re.compile(i, re.I)
                text = re.sub(exp, words[i], text)
        return text

text = replace(words,text)
print text

3
当内建函数可以轻松处理内建字符串方法时,使用内建字符串方法要好于正则表达式模块。
约翰Y

谢谢冷静。我现在时间紧迫,因此您快速简单的解决方案非常适合我。感谢

@John Y:什么是无正则表达式的解决方案?我没看到
雅各布·博格

Kim已经提到过:使用string.lower()方法。其他答案也提到了这一点。注释不利于发布代码,所以我想我会发布自己的答案。
约翰Y

+1该解决方案对我来说是最有效的,因为就我而言,字典键的大小写很重要,仅简单地减小键的大小是不够的。
pleasemorebacon 2015年

0

您可以使用一个划线员来执行dict键不区分大小写的搜索:

>>> input_dict = {'aBc':1, 'xyZ':2}
>>> search_string = 'ABC'
>>> next((value for key, value in input_dict.items() if key.lower()==search_string.lower()), None)
1
>>> search_string = 'EFG'
>>> next((value for key, value in input_dict.items() if key.lower()==search_string.lower()), None)
>>>

您可以将其放入函数中:


def get_case_insensitive_key_value(input_dict, key):
    return next((value for dict_key, value in input_dict.items() if dict_key.lower() == key.lower()), None)


请注意,仅返回第一个匹配项。


0

如果您只需要在代码中执行一次此操作(因此,没有指向函数的意思),那么解决问题的最直接方法是:

小写字母= {key.lower():original_dict中(key,value)的值}

我在这里假设所讨论的dict并不是那么大-复制它可能并不优雅,但是如果它不大,就不会有任何伤害。

相对于@Fred的答案,此方法的优点(尽管也可以)是:当不存在该键时,它产生与dict相同的结果:KeyError。


-1

我只是设置一个函数来处理此问题:

def setLCdict(d, k, v):
    k = k.lower()
    d[k] = v
    return d

myDict = {}

所以代替

myDict['A'] = 1
myDict['B'] = 2

您可以:

myDict = setLCdict(myDict, 'A', 1)
myDict = setLCdict(myDict, 'B', 2)

然后,您可以在查找值之前将其小写,或编写一个函数来查找。

    def lookupLCdict(d, k):
        k = k.lower()
        return d[k]

    myVal = lookupLCdict(myDict, 'a')

如果您想在全局范围内执行此操作,则可能不理想,但如果只是要将其用于此操作的子集,则效果很好。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.