Как выполнить сравнение строк без учета регистра?


573

Как я могу сделать сравнение строк без учета регистра в Python?

Я хотел бы инкапсулировать сравнение обычной строки со строкой репозитория, используя очень простой и Pythonic способ. Я также хотел бы иметь возможность искать значения в dict, хэшированном строками, используя обычные строки python.

Ответы:


596

Предполагая строки ASCII:

string1 = 'Hello'
string2 = 'hello'

if string1.lower() == string2.lower():
    print("The strings are the same (case insensitive)")
else:
    print("The strings are NOT the same (case insensitive)")

71
Это не всегда работает. Для примера рассмотрим две греческие сигмы, одна из которых используется только в конце. Строка Σίσυφος («Sísyphos», или лучше «Síſyphos») имеет все три: верхний регистр спереди, нижний регистр в конце и строчный нефинал в третьей позиции. Если у вас есть две строки Σίσυφοςи ΣΊΣΥΦΟΣ, тогда ваш подход потерпит неудачу, потому что они должны быть одинаковыми без учета регистра.
2012 года

52
@ Последние два комментария: я думаю, что справедливо предположить, что обе строки являются строками ascii. Если вы ищете ответ на что-то более захватывающее, я уверен, что оно есть (или вы можете спросить его).
Харли Холкомб

16
Проблема: 'ß'.lower() == 'SS'.lower()ложно.
Kennytm

11
Греческие буквы - не единственный особый случай! В американском английском языке символ «i» (\ u0069) является строчной версией символа «I» (\ u0049). Тем не менее, турецкий алфавит («tr-TR») содержит символ «I с точкой» «İ» (\ u0130), который является заглавной версией «i», а «I» - заглавная версия «i без точка "символ", "ı" (\ u0131).
Gqqnbig

20
@ HarleyHolcombe как безопасно (или справедливо) предположить, что строки являются ASCII? Вопрос не уточняется, и если в какой-то момент строки вводятся или показываются пользователю, вы должны поддерживать интернационализацию. Несмотря на это, новые программисты будут читать это, и мы должны дать им действительно правильный ответ.
Итан Реесор,

529

Сравнение строк без учета регистра кажется тривиальным, но это не так. Я буду использовать Python 3, так как Python 2 здесь недостаточно развит.

Первое, на что нужно обратить внимание, - это то, что преобразования с удалением регистра в Unicode не являются тривиальными. Есть текст, для которого text.lower() != text.upper().lower(), например "ß":

"ß".lower()
#>>> 'ß'

"ß".upper().lower()
#>>> 'ss'

Но, допустим, вы хотели безошибочно сравнить "BUSSE"и "Buße". Черт возьми, вы, вероятно, тоже хотите сравнить "BUSSE"и сравнить "BUẞE"- это более новая форма капитала. Рекомендуемый способ заключается в использовании casefold:

ул. casefold ()

Вернуть регистр свернутой копии строки. Строки в регистре могут быть использованы для сопоставления без регистра.

Свертывание регистров похоже на нижний регистр, но более агрессивно, потому что оно предназначено для удаления всех различий регистра в строке. [...]

Не просто использовать lower. Если casefoldнет в наличии, делаю.upper().lower() помогает (но только в некоторой степени).

Тогда вы должны рассмотреть акценты. Если ваш рендерер шрифтов хорош, вы, вероятно, думаете, "ê" == "ê"но это не так:

"ê" == "ê"
#>>> False

Это потому, что акцент на последнем является сочетанием характера.

import unicodedata

[unicodedata.name(char) for char in "ê"]
#>>> ['LATIN SMALL LETTER E WITH CIRCUMFLEX']

[unicodedata.name(char) for char in "ê"]
#>>> ['LATIN SMALL LETTER E', 'COMBINING CIRCUMFLEX ACCENT']

Самый простой способ справиться с этим unicodedata.normalize. Вы, вероятно, хотите использовать нормализацию NFKD , но не стесняйтесь проверять документацию. Тогда один

unicodedata.normalize("NFKD", "ê") == unicodedata.normalize("NFKD", "ê")
#>>> True

Чтобы закончить, здесь это выражается в функциях:

import unicodedata

def normalize_caseless(text):
    return unicodedata.normalize("NFKD", text.casefold())

def caseless_equal(left, right):
    return normalize_caseless(left) == normalize_caseless(right)

8
Лучшее решение - нормализовать все ваши строки при приеме, тогда вы можете просто выполнить x.casefold() == y.casefold()сравнение без учета регистра (и, что более важно, x == yс учетом регистра).
abarnert

3
@abarnert Действительно, в зависимости от контекста - иногда лучше оставить исходный код нетронутым, но предварительная нормализация также может сделать более поздний код намного проще.
Veedrac

3
@Veedrac: Вы правы, это не всегда уместно; если вам нужно иметь возможность вывести исходный источник без изменений (например, потому что вы имеете дело с именами файлов в Linux, где NKFC и NKFD разрешены и явно должны отличаться), очевидно, вы не сможете преобразовать его при вводе…
abarnert

7
В разделе 3.13 стандарта Unicode есть два других определения для сравнения без учета регистра: (D146, канонический) NFD(toCasefold(NFD(str)))с обеих сторон и (D147, совместимость) NFKD(toCasefold(NFKD(toCasefold(NFD(X)))))с обеих сторон. В нем говорится, что внутреннее NFDисключительно для обработки определенного греческого символа акцента. Я думаю, это все о крайних случаях.

2
И немного забавно с алфавитом чероки, где casefold () идет в верхнем регистре: >>> "ᏚᎢᎵᎬᎢᎬᏒ". Upper () 'ᏚᎢᎵᎬᎢᎬᏒ' >>> "ᏚᎢᎵᎬᎢᎬᏒ". Lower () 'ꮪꭲꮅꭼꭲꭼꮢ' >>> "ᏚᎢᎵᎬᎢᎬᏒ" .casefold () 'ᏚᎢᎵᎬᎢᎬᏒ' >>>
bortzmeyer

60

Используя Python 2, вызывая .lower()каждую строку или объект Unicode ...

string1.lower() == string2.lower()

... будет работать большую часть времени, но на самом деле не работает в ситуациях, описанных @tchrist .

Предположим, у нас есть файл с именем, unicode.txtсодержащий две строки Σίσυφοςи ΣΊΣΥΦΟΣ. С Python 2:

>>> utf8_bytes = open("unicode.txt", 'r').read()
>>> print repr(utf8_bytes)
'\xce\xa3\xce\xaf\xcf\x83\xcf\x85\xcf\x86\xce\xbf\xcf\x82\n\xce\xa3\xce\x8a\xce\xa3\xce\xa5\xce\xa6\xce\x9f\xce\xa3\n'
>>> u = utf8_bytes.decode('utf8')
>>> print u
Σίσυφος
ΣΊΣΥΦΟΣ

>>> first, second = u.splitlines()
>>> print first.lower()
σίσυφος
>>> print second.lower()
σίσυφοσ
>>> first.lower() == second.lower()
False
>>> first.upper() == second.upper()
True

Символ Σ имеет две строчные формы, ς и σ, и .lower() не поможет сравнить их без учета регистра.

Однако, начиная с Python 3, все три формы будут преобразованы в ς, и вызов метода lower () для обеих строк будет работать правильно:

>>> s = open('unicode.txt', encoding='utf8').read()
>>> print(s)
Σίσυφος
ΣΊΣΥΦΟΣ

>>> first, second = s.splitlines()
>>> print(first.lower())
σίσυφος
>>> print(second.lower())
σίσυφος
>>> first.lower() == second.lower()
True
>>> first.upper() == second.upper()
True

Так что если вы заботитесь о крайних случаях, таких как три сигмы на греческом языке, используйте Python 3.

(Для справки, Python 2.7.3 и Python 3.3.0b1 показаны в распечатках интерпретатора выше.)


20
Чтобы сделать сравнение еще более надежным, начиная с Python 3.3, вы можете использовать casefold (например, first.casefold () == second.casefold ()). Для Python 2 вы можете использовать PyICU (см. Также: icu-project.org/apiref/icu4c/… )
kgriffs

42

Раздел 3.13 стандарта Unicode определяет алгоритмы для сопоставления без учета регистра.

X.casefold() == Y.casefold() в Python 3 реализуется «сопоставление без учета регистра по умолчанию» (D144).

Завершение кейса не сохраняет нормализацию строк во всех случаях, и поэтому нормализация должна быть выполнена ( 'å'против 'å'). D145 вводит "каноническое сопоставление без регистра":

import unicodedata

def NFD(text):
    return unicodedata.normalize('NFD', text)

def canonical_caseless(text):
    return NFD(NFD(text).casefold())

NFD() вызывается дважды для очень редких краевых случаев с участием символа U + 0345.

Пример:

>>> 'å'.casefold() == 'å'.casefold()
False
>>> canonical_caseless('å') == canonical_caseless('å')
True

Также есть сопоставление без учета регистра (D146) для таких случаев, как '㎒'(U + 3392) и « сопоставление без учета идентификатора», чтобы упростить и оптимизировать сопоставление без учета идентификаторов .


3
Это лучший ответ для Python 3, потому что Python 3 использует строки Unicode, и ответ описывает, как стандарт Unicode определяет сопоставление строк без регистра.
СергейКолесников

К сожалению, начиная с Python 3.6, casefold()функция не реализует специальную обработку регистра прописных букв I и прописных букв I, как описано в разделе Свойства сворачивания регистра . Поэтому сравнение может быть неудачным для слов из тюркских языков, содержащих эти буквы. Например, canonical_caseless('LİMANI') == canonical_caseless('limanı')должен вернуться True, но он возвращается False. В настоящее время единственный способ справиться с этим в Python - написать оболочку casefold или использовать внешнюю библиотеку Unicode, такую ​​как PyICU.
СергейКолесников

Насколько я могу судить, @SergiyKolesnikov .casefold () ведет себя как следует. Из стандарта: «Операции с оболочками по умолчанию предназначены для использования при отсутствии адаптации для конкретных языков и сред» . Правила обсадных труб для турецкой точечной столицы I и без точечной буквы I находятся в SpecialCasing.txt. «Для нетюркских языков это отображение обычно не используется». Из FAQ по Unicode: Q: Почему нет дополнительных символов, закодированных для поддержки независимого от локализации корпуса для турецкого языка?
JFS

1
@ jf-sebastian Я не говорил, что casefold () ведет себя плохо. Это было бы практично, если бы в нем был реализован необязательный параметр, который включал специальную обработку прописных и точечных прописных букв I. Например, способ, которым это делает foldCase () в библиотеке ICU : « Завершение регистра не зависит от локали и не зависит от контекста. -чувствителен, но есть возможность включить или исключить сопоставления для точек с точками I и без точек i, помеченных буквой 'T' в CaseFolding.txt. "
СергейКолесников

6

Я видел это решение здесь с помощью регулярных выражений .

import re
if re.search('mandy', 'Mandy Pande', re.IGNORECASE):
# is True

Хорошо работает с акцентами

In [42]: if re.search("ê","ê", re.IGNORECASE):
....:        print(1)
....:
1

Однако он не работает с символами Юникода без учета регистра. Спасибо @Rhymoid за указание на то, что, насколько я понимаю, ему нужен точный символ, чтобы случай был правдой. Вывод следующий:

In [36]: "ß".lower()
Out[36]: 'ß'
In [37]: "ß".upper()
Out[37]: 'SS'
In [38]: "ß".upper().lower()
Out[38]: 'ss'
In [39]: if re.search("ß","ßß", re.IGNORECASE):
....:        print(1)
....:
1
In [40]: if re.search("SS","ßß", re.IGNORECASE):
....:        print(1)
....:
In [41]: if re.search("ß","SS", re.IGNORECASE):
....:        print(1)
....:

4
Тот факт , что ßне найден в SSс нечувствительным к регистру поиска является доказательством того, что он не работает работу с символами Unicode на всех .

3

Обычный подход состоит в том, чтобы прописать строчные или строчные буквы для поиска и сравнения. Например:

>>> "hello".upper() == "HELLO".upper()
True
>>> 

2

Как насчет преобразования в нижний регистр в первую очередь? Вы можете использовать string.lower().


4
Вы не можете сравнивать их строчные карты: Σίσυφοςи ΣΊΣΥΦΟΣне проверяли бы эквивалент, но должны.
tchrist

-2
def insenStringCompare(s1, s2):
    """ Method that takes two strings and returns True or False, based
        on if they are equal, regardless of case."""
    try:
        return s1.lower() == s2.lower()
    except AttributeError:
        print "Please only pass strings into this method."
        print "You passed a %s and %s" % (s1.__class__, s2.__class__)

3
Вы заменяете исключение сообщением, напечатанным на стандартный вывод, а затем возвращаете Нет, что ложно. Это очень бесполезно на практике.
геррит

-2

Все, что вам нужно сделать, это преобразовать две строки в строчные (все буквы становятся строчными), а затем сравнить их (предполагая, что строки являются строками ASCII).

Например:

string1 = "Hello World"
string2 = "hello WorlD"

if string1.lower() == string2.lower():
    print("The two strings are the same.")
else:
    print("The two strings are not the same.")

Этот ответ не добавляет никакой новой информации. Более того, это почти так же, как принятый ответ .
Георгий

-3

Это еще одно регулярное выражение, которое я научился любить / ненавидеть на прошлой неделе, поэтому обычно придаю значение (в данном случае да) тому, что отражает мои чувства! сделать нормальную функцию .... запросить ввод, затем использовать .... что-то = re.compile (r'foo * | spam * ', yes.I) ...... re.I (yes.I ниже) - то же самое, что IGNORECASE, но вы не можете совершить столько ошибок при написании!

Затем вы ищите свое сообщение с помощью регулярных выражений, но, честно говоря, это должно быть несколько отдельных страниц, но дело в том, что foo или spam передаются вместе, а case игнорируется. Затем, если любой из них найден, lost_n_found отобразит один из них. если ни то, ни другое lost_n_found равно None. Если оно не равно ни одному, верните user_input в нижнем регистре, используя «return lost_n_found.lower ()»

Это позволяет вам намного легче сопоставить все, что будет чувствительно к регистру. Наконец (NCS) означает "никто не заботится серьезно ...!" или не чувствительны к регистру .... какой бы ни

если у кого-то есть какие-либо вопросы, дайте мне знать об этом ..

    import re as yes

    def bar_or_spam():

        message = raw_input("\nEnter FoO for BaR or SpaM for EgGs (NCS): ") 

        message_in_coconut = yes.compile(r'foo*|spam*',  yes.I)

        lost_n_found = message_in_coconut.search(message).group()

        if lost_n_found != None:
            return lost_n_found.lower()
        else:
            print ("Make tea not love")
            return

    whatz_for_breakfast = bar_or_spam()

    if whatz_for_breakfast == foo:
        print ("BaR")

    elif whatz_for_breakfast == spam:
        print ("EgGs")
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.