Como faço uma comparação de cadeias sem distinção entre maiúsculas e minúsculas?

573

Como posso fazer comparação de strings sem distinção entre maiúsculas e minúsculas no Python?

Gostaria de encapsular a comparação de cadeias regulares a uma cadeia de repositórios usando de uma maneira muito simples e Python. Eu também gostaria de ter a capacidade de procurar valores em um dict hash por strings usando strings python regulares.

Kozyarchuk
fonte

Respostas:

595

Assumindo cadeias 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)")
Harley Holcombe
fonte
71
Isso nem sempre funciona. Considere, por exemplo, que existem dois sigmas gregos, um usado apenas no final. A sequência Σίσυφος (“Sísyphos”, ou melhor, “Síſyphos”) possui todos os três: maiúsculas na frente, minúsculas na final e minúsculas na final na terceira posição. Se suas duas seqüências de caracteres são Σίσυφοςe ΣΊΣΥΦΟΣ, então, sua abordagem falha, porque essas devem ser o mesmo caso sem sensibilidade.
tchrist
52
@ Os dois últimos comentadores: acho justo assumir que as duas strings são ascii. Se você está procurando uma resposta para algo um pouco mais emocionante, tenho certeza que está lá fora (ou você pode perguntar).
Harley Holcombe
16
Problema: 'ß'.lower() == 'SS'.lower()é falso.
Kennytm
11
As letras gregas não são o único caso especial! No inglês dos EUA, o caractere "i" (\ u0069) é a versão em minúscula do caractere "I" (\ u0049). No entanto, o alfabeto turco ("tr-TR") inclui um caractere "I com um ponto" "©" (\ u0130), que é a versão maiúscula de "i" e "I" é a versão captical de "i sem um ponto "caractere", ı "(\ u0131).
Gqqnbig
20
@HarleyHolcombe como é seguro (ou justo) assumir que as strings são ascii? A pergunta não foi especificada e, se as seqüências de caracteres estiverem em algum momento inseridas ou exibidas para um usuário, você deverá apoiar a internacionalização. Independentemente disso, novos programadores lerão isso e devemos dar a resposta realmente correta.
Ethan Reesor
529

Comparar strings de uma maneira que não diferencia maiúsculas de minúsculas parece trivial, mas não é. Eu vou usar o Python 3, pois o Python 2 é subdesenvolvido aqui.

A primeira coisa a observar é que as conversões de remoção de maiúsculas e minúsculas no Unicode não são triviais. Há texto para o qual text.lower() != text.upper().lower(), como "ß":

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

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

Mas digamos que você queria comparar "BUSSE"e "Buße". Caramba, você provavelmente também quer comparar "BUSSE"e "BUẞE"igualar - essa é a nova forma de capital. A maneira recomendada é usar casefold:

str. casefold ()

Retorne uma cópia dobrada em caixa da sequência. As cordas dobradas em caixa podem ser usadas para correspondência sem caixa.

A dobragem de caixa é semelhante à minúscula, mas é mais agressiva porque se destina a remover todas as distinções de maiúsculas e minúsculas em uma sequência. [...]

Não use apenas lower. Se casefoldnão estiver disponível, fazer .upper().lower()ajuda (mas apenas um pouco).

Então você deve considerar sotaques. Se o seu renderizador de fontes é bom, você provavelmente pensa "ê" == "ê"- mas não:

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

Isso ocorre porque o sotaque desse último é um personagem combinado.

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']

A maneira mais simples de lidar com isso é unicodedata.normalize. Você provavelmente deseja usar a normalização do NFKD , mas fique à vontade para verificar a documentação. Então se faz

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

Para finalizar, aqui isso é expresso em funções:

import unicodedata

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

def caseless_equal(left, right):
    return normalize_caseless(left) == normalize_caseless(right)
Veedrac
fonte
8
Uma solução melhor é normalizar todas as suas seqüências de caracteres na ingestão, então você pode fazer apenas x.casefold() == y.casefold()comparações que não diferenciam maiúsculas de minúsculas (e, mais importante, que x == ydiferenciam maiúsculas de minúsculas).
Abarnert
3
@abarnert De fato, dependendo do contexto - às vezes é melhor deixar a fonte intacta, mas a normalização inicial também pode tornar o código posterior muito mais simples.
Veedrac
3
@Veedrac: Você está certo, nem sempre é apropriado; se você precisa produzir a fonte original inalterada (por exemplo, porque você está lidando com nomes de arquivos no Linux, onde NKFC e NKFD são permitidos e explicitamente supostamente diferentes), obviamente você não pode transformá-la na entrada ...
Abarnert
7
A seção 3.13 do Padrão Unicode possui duas outras definições para comparações sem caixa: (D146, canônico) NFD(toCasefold(NFD(str)))em ambos os lados e (D147, compatibilidade) NFKD(toCasefold(NFKD(toCasefold(NFD(X)))))em ambos os lados. Ele afirma que o interior NFDé apenas para lidar com um certo caráter de sotaque grego. Eu acho que é tudo sobre os casos extremos.
2
E um pouco de diversão com o alfabeto Cherokee, onde casefold () vai para maiúsculas: >>> "ᏚᎢᎵᎬᎢᎬᏒ". Upper () 'ᏚᎢᎵᎬᎢᎬᏒ' >>> "ᏚᎢᎵᎬᎢᎬᏒ". Lower () 'ꮪꭲꮅꭼꭲꭼꮢ' >>> "ᏚᎢᎵᎬᎢᎬᏒ" .casefold () 'ᏚᎢᎵᎬᎢᎬᏒ' >>>
bortzmeyer 2/17/17
60

Usando Python 2, chamando .lower()cada string ou objeto Unicode ...

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

... funcionará a maior parte do tempo, mas na verdade não funciona nas situações descritas por tchrist .

Suponha que temos um arquivo chamado unicode.txtcontendo as duas strings Σίσυφοςe ΣΊΣΥΦΟΣ. Com o 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

O caractere has possui duas formas minúsculas, ς e σ, e .lower()não ajudará a compará-las sem distinção entre maiúsculas e minúsculas.

No entanto, a partir do Python 3, todos os três formulários serão resolvidos como ς, e chamar lower () em ambas as strings funcionará corretamente:

>>> 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

Portanto, se você se importa com casos extremos como os três sigmas em grego, use Python 3.

(Para referência, Python 2.7.3 e Python 3.3.0b1 são mostrados nas impressões do interpretador acima.)

Nathan Craike
fonte
20
Para tornar a comparação ainda mais robusta, a partir do Python 3.3, você pode usar casefold (por exemplo, first.casefold () == second.casefold ()). Para Python 2 você pode usar PyICU (ver também: icu-project.org/apiref/icu4c/... )
kgriffs
42

A Seção 3.13 do padrão Unicode define algoritmos para correspondência sem caixa.

X.casefold() == Y.casefold() no Python 3 implementa a "correspondência padrão sem caixa" (D144).

Casefolding não preserva a normalização de strings em todas as instâncias e, portanto, a normalização precisa ser feita ( 'å'vs. 'å'). O D145 apresenta a "correspondência canônica sem caixa":

import unicodedata

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

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

NFD() é chamado duas vezes para casos de borda muito pouco frequentes envolvendo o caractere U + 0345.

Exemplo:

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

Também há compatibilidade sem caixa de compatibilidade (D146) para casos como '㎒'(U + 3392) e "correspondência sem caixa de identificador" para simplificar e otimizar a correspondência sem caixa de identificadores .

jfs
fonte
3
Esta é a melhor resposta para o Python 3, porque o Python 3 usa cadeias Unicode e a resposta descreve como o padrão Unicode define a correspondência de cadeias sem caixa.
SergiyKolesnikov
Infelizmente, a partir do Python 3.6, a casefold()função não implementa o tratamento especial de maiúsculas I e maiúsculas pontilhadas I, conforme descrito em Case Folding Properties . Portanto, a comparação pode falhar para palavras de idiomas turcos que contenham essas letras. Por exemplo, canonical_caseless('LİMANI') == canonical_caseless('limanı')deve retornar True, mas retorna False. Atualmente, a única maneira de lidar com isso no Python é escrever um wrapper de casefold ou usar uma biblioteca Unicode externa, como PyICU.
SergiyKolesnikov
@SergiyKolesnikov .casefold () se comporta como deveria, tanto quanto eu sei. A partir do padrão: "as operações de revestimento padrão destinam-se ao uso na ausência de adaptação para idiomas e ambientes específicos" . As regras de maiúsculas e minúsculas para a capital pontilhada turca I e para pequenas i i estão em SpecialCasing.txt. "Para idiomas não turcos, esse mapeamento normalmente não é usado." Nas Perguntas frequentes sobre Unicode: P: Por que não há caracteres extras codificados para dar suporte à caixa independente de localidade para turco?
JFS
1
@ jf-sebastian Eu não disse que casefold () se comporta mal. Seria prático se implementasse um parâmetro opcional que habilitasse o tratamento especial de maiúsculas e maiúsculas I. Por exemplo, a maneira como foldCase () na biblioteca da UTI faz isso : "A dobra de maiúsculas e minúsculas é independente da localidade e não do contexto -sensível, mas existe uma opção para incluir ou excluir mapeamentos para I pontilhado e i sem pontos marcados com 'T' em CaseFolding.txt. "
SergiyKolesnikov
6

Eu vi essa solução aqui usando regex .

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

Funciona bem com sotaques

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

No entanto, ele não funciona com caracteres unicode que não diferenciam maiúsculas de minúsculas. Obrigado @Rhymoid por apontar que, como eu entendi, ele precisa do símbolo exato, para que o caso seja verdadeiro. A saída é a seguinte:

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)
....:
Shiwangi
fonte
4
O fato de que ßnão é encontrado dentro SScom busca case-insensitive evidências de que ele não funciona o trabalho com caracteres Unicode em tudo .
3

A abordagem usual é colocar as cadeias em maiúsculas ou minúsculas para as pesquisas e comparações. Por exemplo:

>>> "hello".upper() == "HELLO".upper()
True
>>> 
Andru Luvisi
fonte
2

Que tal converter primeiro para minúsculas? você pode usar string.lower().

Camilo Díaz Repka
fonte
4
Você não pode comparar seus mapas em minúsculas: Σίσυφοςe ΣΊΣΥΦΟΣnão testaria equivalente, mas deveria.
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__)
Patrick Harrington
fonte
3
Você está substituindo um exceto por uma mensagem impressa em stdout e retornando Nenhum, que é Falso. Isso é muito inútil na prática.
gerrit
-2

Tudo o que você precisa fazer é converter as duas strings em minúsculas (todas as letras se tornam minúsculas) e depois compará-las (assumindo que as strings sejam ASCII).

Por exemplo:

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.")
Rohit Karthik
fonte
Esta resposta não adiciona nenhuma informação nova. Além disso, é quase o mesmo que a resposta aceita .
Georgy
-3

Este é outro regex que aprendi a amar / odiar na última semana, então geralmente importo como (neste caso, sim) algo que reflete como estou me sentindo! faça uma função normal .... solicite entrada e use .... algo = re.compile (r'foo * | spam * ', yes.I) ...... re.I (yes.I abaixo) é o mesmo que IGNORECASE, mas você não pode cometer tantos erros ao escrevê-lo!

Em seguida, você pesquisa sua mensagem usando regex, mas honestamente isso deve ter algumas páginas, mas o ponto é que foo ou spam são canalizados juntos e a caixa é ignorada. Então, se um deles for encontrado, lost_n_found exibirá um deles. se nenhum deles, lost_n_found for igual a None. Se não for igual a nenhum, retorne o user_input em letras minúsculas usando "return lost_n_found.lower ()"

Isso permite que você combine com mais facilidade qualquer coisa que diferencia maiúsculas de minúsculas. Por fim (NCS), significa "ninguém se importa seriamente ...!" ou não diferencia maiúsculas de minúsculas ...

se alguém tiver alguma dúvida me fale sobre isso ..

    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")
Ali Paul
fonte