¿Cómo hago una comparación de cadenas que no distingue entre mayúsculas y minúsculas?

573

¿Cómo puedo hacer una comparación de cadenas insensible a mayúsculas y minúsculas en Python?

Me gustaría encapsular la comparación de una cadena regular con una cadena de repositorio usando de una manera muy simple y pitónica. También me gustaría tener la capacidad de buscar valores en un dict hash por cadenas usando cadenas de python regulares.

Kozyarchuk
fuente

Respuestas:

595

Suponiendo cadenas 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
fuente
71
Eso no siempre funciona. Considere, por ejemplo, que hay dos sigmas griegos, uno solo usado al final. La cadena Σίσυφος ("Sísyphos", o mejor "Síſyphos") tiene los tres: mayúscula al frente, minúscula final al final y minúscula no final en la tercera posición. Si sus dos cadenas son Σίσυφοςy ΣΊΣΥΦΟΣ, entonces su enfoque falla, porque se supone que esas son las mismas mayúsculas y minúsculas.
tchrist
52
@ Los dos últimos comentaristas: creo que es justo asumir que ambas cadenas son cadenas ascii. Si está buscando una respuesta a algo un poco más emocionante, estoy seguro de que está disponible (o puede preguntar).
Harley Holcombe
16
Problema: 'ß'.lower() == 'SS'.lower()es falso.
kennytm
11
¡Las letras griegas no son el único caso especial! En inglés de EE. UU., El carácter "i" (\ u0069) es la versión en minúsculas del carácter "I" (\ u0049). Sin embargo, el alfabeto turco ("tr-TR") incluye un carácter "I con un punto" "İ" (\ u0130), que es la versión capital de "i" y "I" es la versión cautiva de "i sin un punto "carácter", "i" (\ u0131).
Gqqnbig
20
@HarleyHolcombe, ¿cómo es seguro (o justo) asumir que las cadenas son ascii? La pregunta no especificó, y si las cadenas son ingresadas o mostradas a un usuario en algún momento, entonces debe apoyar la internacionalización. De todos modos, los nuevos programadores leerán esto y debemos darles la respuesta verdaderamente correcta.
Ethan Reesor
529

Comparar cadenas de una manera insensible a mayúsculas y minúsculas parece trivial, pero no lo es. Usaré Python 3, ya que Python 2 está subdesarrollado aquí.

Lo primero a tener en cuenta es que las conversiones de eliminación de mayúsculas y minúsculas en Unicode no son triviales. Hay texto para el cual text.lower() != text.upper().lower(), como "ß":

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

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

Pero digamos que querías comparar "BUSSE"y sin caso "Buße". Demonios, probablemente también quieras comparar "BUSSE"e "BUẞE"igualar, esa es la nueva forma de capital. La forma recomendada es usar casefold:

str. casefold ()

Devuelve una copia de la cadena con mayúsculas y minúsculas. Las cadenas plegadas en mayúsculas y minúsculas se pueden usar para la coincidencia sin mayúsculas y minúsculas.

El plegado de mayúsculas y minúsculas es similar a las minúsculas pero más agresivo porque tiene la intención de eliminar todas las distinciones de mayúsculas y minúsculas en una cadena. [...]

No solo lo use lower. Si casefoldno está disponible, hacer .upper().lower()ayuda (pero solo un poco).

Entonces deberías considerar los acentos. Si su renderizador de fuentes es bueno, probablemente piense "ê" == "ê", pero no es así:

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

Esto se debe a que el acento en este último es un carácter 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']

La forma más sencilla de lidiar con esto es unicodedata.normalize. Probablemente desee utilizar la normalización de NFKD , pero no dude en consultar la documentación. Entonces uno hace

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

Para terminar, aquí se expresa en funciones:

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
fuente
8
Una mejor solución es normalizar todas sus cadenas en la ingesta, luego puede hacerlo x.casefold() == y.casefold()para comparaciones que no distinguen entre mayúsculas y minúsculas (y, lo que es más importante, x == ypara mayúsculas y minúsculas).
abarnert
3
@abarnert De hecho, dependiendo del contexto, a veces es mejor dejar la fuente intacta, pero la normalización inicial también puede hacer que el código posterior sea mucho más simple.
Veedrac
3
@Veedrac: Tienes razón, no siempre es apropiado; si necesita poder generar la fuente original sin cambios (por ejemplo, porque está tratando con nombres de archivos en Linux, donde NKFC y NKFD están permitidos y se supone explícitamente que son diferentes), obviamente no puede transformarlo en la entrada ...
abarnert
77
La sección 3.13 de Unicode Standard tiene otras dos definiciones para comparaciones sin mayúsculas y minúsculas: (D146, canónica) NFD(toCasefold(NFD(str)))en ambos lados y (D147, compatibilidad) NFKD(toCasefold(NFKD(toCasefold(NFD(X)))))en ambos lados. Establece que el interior NFDes únicamente para manejar un cierto carácter de acento griego. Supongo que se trata de casos extremos.
2
Y un poco divertido con el alfabeto Cherokee, donde casefold () va en mayúscula: >>> "ᏚᎢᎵᎬᎢᎬᏒ". Upper () 'ᏚᎢᎵᎬᎢᎬᏒ' >>> "ᏚᎢᎵᎬᎢᎬᏒ". Lower () 'ꮪꭲꮅꭼꭲꭼꮢ' >>> "ᏚᎢᎵᎬᎢᎬᏒ" .casefold () 'ᏚᎢᎵᎬᎢᎬᏒ' >>>
bortzmeyer
60

Usando Python 2, invocando .lower()cada cadena u objeto Unicode ...

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

... funcionará la mayor parte del tiempo, pero de hecho no funciona en las situaciones que @tchrist ha descrito .

Supongamos que tenemos un archivo llamado que unicode.txtcontiene las dos cadenas Σίσυφοςy ΣΊΣΥΦΟΣ. Con 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

El carácter Σ tiene dos formas en minúsculas, ς y σ, y .lower()no ayudará a compararlas entre mayúsculas y minúsculas.

Sin embargo, a partir de Python 3, las tres formas se resolverán en ς, y llamar a lower () en ambas cadenas funcionará correctamente:

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

Entonces, si te interesan los casos extremos como los tres sigmas en griego, usa Python 3.

(Como referencia, Python 2.7.3 y Python 3.3.0b1 se muestran en las impresiones del intérprete anteriores).

Nathan Craike
fuente
20
Para hacer la comparación aún más robusta, comenzando con Python 3.3 puede usar casefold (por ejemplo, first.casefold () == second.casefold ()). Para Python 2 puede usar PyICU (ver también: icu-project.org/apiref/icu4c/… )
kgriffs
42

La sección 3.13 del estándar Unicode define algoritmos para la coincidencia sin mayúsculas y minúsculas.

X.casefold() == Y.casefold() en Python 3 implementa la "coincidencia sin mayúsculas predeterminada" (D144).

El plegado de casos no preserva la normalización de las cadenas en todos los casos y, por lo tanto, la normalización debe hacerse ( 'å'vs. 'å'). D145 presenta la "coincidencia sin mayúsculas canónica":

import unicodedata

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

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

NFD() se llama dos veces para casos extremos muy poco frecuentes que involucran el carácter U + 0345.

Ejemplo:

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

También hay compatibilidad sin casillas (D146) para casos como '㎒'(U + 3392) y "coincidencia sin identificador" para simplificar y optimizar la coincidencia sin case de identificadores .

jfs
fuente
3
Esta es la mejor respuesta para Python 3, porque Python 3 usa cadenas Unicode y la respuesta describe cómo el estándar Unicode define la coincidencia de cadenas sin mayúsculas y minúsculas.
SergiyKolesnikov
Desafortunadamente, a partir de Python 3.6, la casefold()función no implementa el tratamiento de mayúsculas y minúsculas I y mayúsculas I como se describe en Propiedades de plegado de mayúsculas y minúsculas . Por lo tanto, la comparación puede fallar para palabras de idiomas turcos que contienen esas letras. Por ejemplo, canonical_caseless('LİMANI') == canonical_caseless('limanı')debe volver True, pero vuelve False. Actualmente, la única forma de lidiar con esto en Python es escribir un contenedor de plegado de casos o usar una biblioteca Unicode externa, como PyICU.
SergiyKolesnikov
@SergiyKolesnikov .casefold () se comporta como debería por lo que puedo decir. Desde el estándar: "las operaciones de carcasa predeterminadas están destinadas a utilizarse en ausencia de adaptación para idiomas y entornos particulares" . Las reglas de mayúsculas y minúsculas para la capital con puntos turca I y las pequeñas sin punto i están en SpecialCasing.txt. "Para los idiomas no turcos, este mapeo normalmente no se usa". De las Preguntas frecuentes de Unicode: P: ¿Por qué no hay caracteres adicionales codificados para admitir la carcasa independiente del entorno local para el turco?
jfs
1
@ jf-sebastian No dije que casefold () se portara mal. Sería práctico si implementara un parámetro opcional que permitiera el tratamiento especial de mayúsculas y mayúsculas con puntos I. Por ejemplo, la forma en que foldCase () en la biblioteca de la UCI lo hace : "El plegado de mayúsculas y minúsculas es independiente de la configuración regional y no del contexto -sensible, pero hay una opción para incluir o excluir asignaciones para I con puntos e I sin puntos que están marcadas con 'T' en CaseFolding.txt ".
SergiyKolesnikov
6

Vi esta solución aquí usando regex .

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

Funciona bien con acentos

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

Sin embargo, no funciona con caracteres unicode que no distinguen entre mayúsculas y minúsculas. Gracias @Rhymoid por señalar que, según tengo entendido, necesita el símbolo exacto, para que el caso sea cierto. El resultado es el siguiente:

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
fuente
44
El hecho de que ßno se encuentre dentro SSde la búsqueda sin distinción entre mayúsculas y minúsculas es evidencia de que no funciona en absoluto con los caracteres Unicode .
3

El enfoque habitual es utilizar mayúsculas o minúsculas para las búsquedas y comparaciones. Por ejemplo:

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

¿Qué hay de convertir a minúsculas primero? puedes usar string.lower().

Camilo Díaz Repka
fuente
44
No puede comparar sus mapas en minúsculas: Σίσυφοςy ΣΊΣΥΦΟΣno probaría equivalentes, pero debería.
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
fuente
3
Está reemplazando una excepción por un mensaje impreso en stdout, luego devuelve None, que es False. Eso es muy inútil en la práctica.
gerrit
-2

Todo lo que tendrá que hacer es convertir las dos cadenas a minúsculas (todas las letras se convierten en minúsculas) y luego compararlas (suponiendo que las cadenas sean cadenas ASCII).

Por ejemplo:

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
fuente
Esta respuesta no agrega ninguna información nueva. Además, es casi lo mismo que la respuesta aceptada .
Georgy
-3

Esta es otra expresión regular que he aprendido a amar / odiar durante la última semana, por lo que generalmente importo como (en este caso sí) algo que refleja cómo me siento. hacer una función normal ... pedir entrada, luego usar .... something = re.compile (r'foo * | spam * ', yes.I) ...... re.I (yes.I a continuación) es lo mismo que IGNORECASE pero no puede cometer tantos errores al escribirlo.

Luego, busca su mensaje utilizando expresiones regulares, pero honestamente, eso debería ser solo unas pocas páginas, pero el punto es que el correo no deseado o spam se juntan y se ignora el caso. Luego, si se encuentra alguno, lost_n_found mostrará uno de ellos. si ninguno de los dos, lost_n_found es igual a Ninguno. Si no es igual a ninguno, devuelva el user_input en minúsculas usando "return lost_n_found.lower ()"

Esto le permite emparejar mucho más fácilmente cualquier cosa que sea sensible a mayúsculas y minúsculas. Por último (NCS) significa "a nadie le importa en serio ...!" o no distingue entre mayúsculas y minúsculas ... lo que sea

si alguien tiene alguna pregunta, hágame saber esto ...

    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
fuente