Нечувствительный к регистру словарь
Я бы хотел, чтобы мой словарь был нечувствительным к регистру.
У меня есть пример кода:
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()]
но я не уверен, как лучше всего интегрировать его в мой существующий код. (Если бы это был разумный, простой подход).
Ответы
Ответ 1
Если я правильно вас понимаю и вы хотите использовать ключевые словари нечувствительным к регистру, один из способов заключается в подклассе 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())
Ответ 2
одобренный в настоящее время ответ не работает для случаев много, поэтому он не может использоваться как вставной 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
Только для записи. Я нашел потрясающую импликацию на Requests:
https://github.com/kennethreitz/requests/blob/v1.2.3/requests/structures.py#L37
Ответ 4
Не могли бы вы использовать string.lower()
на своих входах и используя полностью строчный словарь? Это немного хакерское решение, но оно работает
Ответ 5
В моем конкретном случае мне нужен был нечувствительный к регистру поиск, однако я не хотел изменять исходный случай ключа. Например:
>>> 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 в качестве ключа при использовании словаря.
Ответ 6
В то время как словарь, нечувствительный к регистру, является решением, и есть ответы на вопрос о том, как этого достичь, в этом случае возможно более простой способ. Нечувствительный к регистру поиск достаточен:
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
Ответ 7
Я изменил простое, но хорошее решение pleasemorebacon (спасибо!), Сделав его немного более компактным, автономным и с небольшими обновлениями, позволяющими строить из {'a':1, 'B':2}
и поддерживать протокол __contains__
. Наконец, поскольку ожидается, что CaseInsensitiveDict.Key
будет строкой (что еще может быть чувствительным к регистру или нет), хорошая идея - вывести класс Key
из str
, тогда можно, например, сбросить CaseInsensitiveDict
с json.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()])
Ответ 8
Вы можете выполнять поиск без учета регистра в одном ключе:
>>> 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)
Обратите внимание, что возвращается только первое совпадение.
Ответ 9
Если вам нужно сделать это только один раз в своем коде (следовательно, нет никакого смысла в функции), самый простой способ решить проблему - это:
lowercase_dict = {key.lower(): значение для (key, value) в original_dict}
Я предполагаю здесь, что рассматриваемый диктат не настолько велик - возможно, будет нелегко дублировать его, но если он не большой, он ничего не повредит.
Преимущество этого ответа перед @Fred (хотя это также работает) состоит в том, что он дает тот же результат, что и dict, когда ключ отсутствует: KeyError.
Ответ 10
Я просто создал функцию для обработки этого:
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')
Вероятно, это не идеальный вариант, если вы хотите сделать это глобально, но хорошо работает, если это просто подмножество, для которого вы хотите его использовать.