Нечувствительный к регистру строковый класс в 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}
Другие вопросы по тегам