Нечувствительный к регистру строковый класс в Python
Мне нужно выполнить сравнение строк без учета регистра в python в наборах и словарных ключах. Теперь, создание наборов и подклассов dict, которые не чувствительны к регистру, оказывается на удивление хитрым (см. Словарь нечувствительных к регистру слов, обратите внимание, что все они используют ниже - эй, есть даже отклоненный PEP, хотя его область действия немного шире). Итак, я пошел с созданием нечувствительного к регистру строкового класса (используя этот ответ @AlexMartelli):
class CIstr(unicode):
"""Case insensitive with respect to hashes and comparisons string class"""
#--Hash/Compare
def __hash__(self):
return hash(self.lower())
def __eq__(self, other):
if isinstance(other, basestring):
return self.lower() == other.lower()
return NotImplemented
def __ne__(self, other): return not (self == other)
def __lt__(self, other):
if isinstance(other, basestring):
return self.lower() < other.lower()
return NotImplemented
def __ge__(self, other): return not (self < other)
def __gt__(self, other):
if isinstance(other, basestring):
return self.lower() > other.lower()
return NotImplemented
def __le__(self, other): return not (self > other)
Я полностью осознаю, что lower
на самом деле недостаточно, чтобы покрыть все случаи сравнения строк в юникоде, но я рефакторинг существующего кода, который использовал гораздо более грубый класс для сравнения строк (память и скорость), который в любом случае использовал lower() - так что я могу исправить это на более позднем этапе - плюс я на питоне 2 (как видно по unicode
). Мои вопросы:
я правильно понял операторов?
Достаточно ли этого класса для моих целей, учитывая, что я стараюсь создавать ключи в dicts и устанавливать элементы как
CIstr
случаи - мои цели - проверка равенства, сдерживания, различий между множествами и аналогичных операций без учета регистра. Или я что-то упустил?стоит ли кешировать строчную версию строки (как видно, например, из этого древнего рецепта Python: строки без учета регистра). Этот комментарий говорит о том, что нет - плюс я хочу, чтобы конструкция была максимально быстрой, а размер - как можно меньше, но люди, кажется, включают это.
Советы по совместимости с Python 3 приветствуются!
Крошечная демоверсия:
d = {CIstr('A'): 1, CIstr('B'): 2}
print 'a' in d # True
s = set(d)
print {'a'} - s # set([])
3 ответа
В вашем демо вы используете 'a'
искать вещи в вашем наборе. Это не сработает, если вы попытаетесь использовать 'A'
, так как 'A'
имеет другой хэш. Также 'A' in d.keys()
было бы правдой, но 'A' in d
будет ложным. По сути, вы создали тип, который нарушает обычный контракт всех хешей, утверждая, что он равен объектам с разными хешами.
Вы могли бы объединить этот ответ с ответами о создании специализированных диктов, и иметь диктофон, который преобразует любой возможный ключ в CIstr
прежде чем пытаться найти его. Тогда все ваши CIstr
преобразования могут быть скрыты внутри класса словаря.
Например
class CaseInsensitiveDict(dict):
def __setitem__(self, key, value):
super(CaseInsensitiveDict, self).__setitem__(convert_to_cistr(key), value)
def __getitem__(self, key):
return super(CaseInsensitiveDict, self).__getitem__(convert_to_cistr(key))
# __init__, __contains__ etc.
(На основе /questions/25014649/slovar-bez-ucheta-registra/25014661#25014661)
Код в основном выглядит хорошо. Я бы исключил короткие пути в __ge__
, __le__
, а также __ne__
и разверните их для непосредственного вызова lower().
Ярлык выглядит как то, что делается в `functools.total_ordering (), но он просто замедляет код и усложняет тестирование сравнений между типами, что сложно сделать правильно, когда методы взаимозависимы.
Если кто-то искал решение для Python 3, один из самых чистых и простых способов решить эту проблему — определить класс строки в нижнем регистре:
>>> class lcstr(str):
... """Lowercase string"""
... def __new__(cls, v) -> 'lcstr':
... return super().__new__(cls, v.lower())
...
>>> lcstr('Any STRING')
'any string'
>>> type(_)
<class '__main__.lcstr'>
А затем просто поместите его в диктофон как есть:
>>> {lcstr('ONE'): 1}
{'one': 1}