Come faccio un confronto di stringhe senza distinzione tra maiuscole e minuscole?


573

Come posso fare un confronto tra maiuscole e minuscole e maiuscole e minuscole in Python?

Vorrei incapsulare il confronto di stringhe regolari con una stringa di repository usando in modo molto semplice e pitonico. Vorrei anche avere la possibilità di cercare valori in un dict con hash delle stringhe usando stringhe di pitone regolari.

Risposte:


595

Supponendo stringhe 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
Non sempre funziona. Consideriamo ad esempio che ci sono due sigmi greci, uno usato solo alla fine. La stringa Σίσυφος ("Sísyphos", o meglio "Síſyphos") ha tutte e tre: maiuscole nella parte anteriore, minuscole finali alla fine e minuscole non finali nella terza posizione. Se le tue due stringhe sono Σίσυφοςe ΣΊΣΥΦΟΣ, allora il tuo approccio fallisce, perché si suppone che siano lo stesso caso insensibilmente.
tchrist,

52
@ Gli ultimi due commentatori: penso sia giusto supporre che entrambe le stringhe siano stringhe ASCII. Se stai cercando una risposta a qualcosa di un po 'più eccitante, sono sicuro che è là fuori (o puoi chiederlo).
Harley Holcombe,

16
Problema: 'ß'.lower() == 'SS'.lower()è falso.
kennytm,

11
Le lettere greche non sono l'unico caso speciale! In inglese americano, il carattere "i" (\ u0069) è la versione minuscola del carattere "I" (\ u0049). Tuttavia, l'alfabeto turco ("tr-TR") include un carattere "I con un punto" "İ" (\ u0130), che è la versione maiuscola di "i" e "I" è la versione captiva di "i senza un punto "carattere", "ı" (\ u0131).
Gqqnbig,

20
@HarleyHolcombe come è sicuro (o giusto) supporre che le stringhe siano ascii? La domanda non è stata specificata e se le stringhe sono in qualsiasi momento inserite o mostrate a un utente, allora dovresti supportare l'internazionalizzazione. Indipendentemente da ciò, i nuovi programmatori leggeranno questo e dovremmo dare loro la risposta veramente corretta.
Ethan Reesor,

529

Confrontare le stringhe in modo insensibile alle maiuscole sembra banale, ma non lo è. Userò Python 3, poiché Python 2 è sottosviluppato qui.

La prima cosa da notare è che le conversioni che rimuovono i casi in Unicode non sono banali. Esiste un testo per il quale text.lower() != text.upper().lower(), ad esempio "ß":

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

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

Ma supponiamo che tu voglia confrontare senza casaccio "BUSSE"e "Buße". Diamine, probabilmente vorrai anche confrontare "BUSSE"e "BUẞE"uguagliare: questa è la forma di capitale più recente. Il modo raccomandato è usare casefold:

str. casefold ()

Restituisce una copia piegata a maiuscolo della stringa. Le stringhe piegate a lettere possono essere utilizzate per la corrispondenza senza casing.

Il casefolding è simile al minuscolo ma più aggressivo perché è destinato a rimuovere tutte le distinzioni del caso in una stringa. [...]

Non usare solo lower. Se casefoldnon è disponibile, fare .upper().lower()aiuti (ma solo un po ').

Quindi dovresti considerare gli accenti. Se il tuo renderizzatore di caratteri è buono, probabilmente pensi "ê" == "ê", ma non lo fa:

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

Questo perché l'accento su quest'ultimo è un personaggio che combina.

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

Il modo più semplice per affrontarlo è unicodedata.normalize. Probabilmente vuoi usare la normalizzazione NFKD , ma sentiti libero di controllare la documentazione. Quindi uno lo fa

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

Per finire, qui questo è espresso in funzioni:

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
Una soluzione migliore è quella di normalizzare tutte le stringhe sull'assunzione, quindi puoi fare solo x.casefold() == y.casefold()per confronti senza distinzione tra maiuscole e minuscole (e, soprattutto, x == yper distinzione tra maiuscole e minuscole).
abarnert,

3
@abarnert In effetti, a seconda del contesto - a volte è meglio lasciare intatta la fonte, ma la normalizzazione iniziale può anche rendere il codice successivo molto più semplice.
Veedrac,

3
@Veedrac: hai ragione, non è sempre appropriato; se devi essere in grado di produrre la sorgente originale invariata (ad es. perché hai a che fare con nomi di file su Linux, dove NKFC e NKFD sono entrambi consentiti e si suppone esplicitamente di essere diversi), ovviamente non puoi trasformarlo in input ...
abarnert,

7
La sezione 3.13 di Unicode Standard ha altre due definizioni per i confronti senza casing: (D146, canonico) NFD(toCasefold(NFD(str)))su entrambi i lati e (D147, compatibilità) NFKD(toCasefold(NFKD(toCasefold(NFD(X)))))su entrambi i lati. Afferma che l'interno NFDè solo quello di gestire un certo carattere di accento greco. Immagino si tratti solo di casi limite.

2
E un po 'di divertimento con l'alfabeto Cherokee, dove la cartella () diventa maiuscola: >>> "ᏚᎢᎵᎬᎢᎬᏒ". Upper ()' ᏚᎢᎵᎬᎢᎬᏒ '>>> "ᏚᎢᎵᎬᎢᎬᏒ". Lower ()' ꮪꭲꮅꭼꭲꭼꮢ '>>> "ᏚᎢᎵᎬᎢᎬᏒ" .casefold () 'ᏚᎢᎵᎬᎢᎬᏒ' >>>
bortzmeyer,

60

Usando Python 2, chiamando .lower()ogni stringa o oggetto Unicode ...

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

... funzionerà per la maggior parte del tempo, ma in effetti non funziona nelle situazioni descritte da @tchrist .

Supponiamo di avere un file chiamato unicode.txtcontenente le due stringhe Σίσυφοςe ΣΊΣΥΦΟΣ. 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

Il carattere Σ ha due forme minuscole, ς e σ, e .lower()non aiuta a confrontarle senza distinzione tra maiuscole e minuscole.

Tuttavia, a partire da Python 3, tutte e tre le forme si risolveranno in ς e la chiamata lower () su entrambe le stringhe funzionerà correttamente:

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

Quindi, se ti interessano i casi limite come i tre sigmi in greco, usa Python 3.

(Per riferimento, Python 2.7.3 e Python 3.3.0b1 sono mostrati nelle stampe dell'interprete sopra.)


20
Per rendere il confronto ancora più solido, a partire da Python 3.3 è possibile utilizzare casefold (ad esempio, first.casefold () == second.casefold ()). Per Python 2 puoi usare PyICU (vedi anche: icu-project.org/apiref/icu4c/… )
kgriffs

42

La sezione 3.13 dello standard Unicode definisce gli algoritmi per la corrispondenza senza caseless.

X.casefold() == Y.casefold() in Python 3 implementa la "corrispondenza caseless predefinita" (D144).

Il casefolding non preserva la normalizzazione delle stringhe in tutti i casi e pertanto è necessario eseguire la normalizzazione ( 'å'vs. 'å'). D145 introduce la "corrispondenza caneless caseless":

import unicodedata

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

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

NFD() viene chiamato due volte per casi limite molto rari che coinvolgono il carattere U + 0345.

Esempio:

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

Esistono anche la compatibilità senza caseless (D146) per casi come '㎒'(U + 3392) e "identificatore caseless matching" per semplificare e ottimizzare la corrispondenza caseless degli identificatori .


3
Questa è la risposta migliore per Python 3, perché Python 3 utilizza stringhe Unicode e la risposta descrive come lo standard Unicode definisce la corrispondenza delle stringhe senza cas.
SergiyKolesnikov,

Sfortunatamente, a partire da Python 3.6, la casefold()funzione non implementa il trattamento di maiuscole / minuscole in maiuscolo I e I maiuscole tratteggiate come descritto in Proprietà di piegatura del case . Pertanto, il confronto potrebbe non riuscire per le parole delle lingue turche che contengono quelle lettere. Ad esempio, canonical_caseless('LİMANI') == canonical_caseless('limanı')deve restituire True, ma restituisce False. Attualmente, l'unico modo per gestirlo in Python è scrivere un wrapper a cartelle o usare una libreria Unicode esterna, come PyICU.
SergiyKolesnikov il

@SergiyKolesnikov .casefold () si comporta come dovrebbe per quanto posso dire. Dallo standard: "le operazioni di involucro predefinite sono destinate all'uso in assenza di adattamenti per particolari linguaggi e ambienti" . Le regole di casing per la capitale tratteggiata turca I e la piccola punteggiata sono in SpecialCasing.txt. "Per le lingue non turche, questa mappatura non viene normalmente utilizzata." Dalle domande frequenti Unicode: D: Perché non ci sono caratteri aggiuntivi codificati per supportare il case indipendente dalla locale per il turco?
jfs,

1
@ jf-sebastian Non ho detto che casefold () si comporta male. Sarebbe solo pratico se implementasse un parametro opzionale che abilitasse il trattamento speciale di maiuscole e punteggiate maiuscole I. Ad esempio, il modo in cui lo fa foldCase () nella libreria ICU lo fa : "La piegatura dei casi è indipendente dalla locale e non dal contesto -sensibile, ma esiste un'opzione per includere o escludere mappature per I punteggiato e i senza punto che sono contrassegnati con 'T' in CaseFolding.txt. "
SergiyKolesnikov,

6

Ho visto questa soluzione qui usando regex .

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

Funziona bene con gli accenti

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

Tuttavia, non funziona con i caratteri unicode senza distinzione tra maiuscole e minuscole. Grazie @Rhymoid per aver sottolineato che, come ho capito, aveva bisogno del simbolo esatto, perché il caso fosse vero. L'output è il seguente:

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
Il fatto che ßnon si trova all'interno SSdi ricerca case-insensitive è la prova che esso non funziona il lavoro con i caratteri Unicode a tutti .

3

L'approccio usuale è di mettere in maiuscolo le stringhe o di minuscole per le ricerche e i confronti. Per esempio:

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

2

Che ne dici di convertire prima in minuscolo? puoi usare string.lower().


4
Non puoi confrontare le loro mappe minuscole: Σίσυφοςe ΣΊΣΥΦΟΣnon testerebbero l'equivalente, ma dovrebbero.
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
Stai sostituendo un'eccezione con un messaggio stampato su stdout, quindi restituendo None, che è False. È molto inutile in pratica.
Gerrit,

-2

Tutto quello che dovrai fare è convertire le due stringhe in minuscolo (tutte le lettere diventano minuscole) e quindi confrontarle (supponendo che le stringhe siano stringhe ASCII).

Per esempio:

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.")

Questa risposta non aggiunge nuove informazioni. Inoltre, è quasi uguale alla risposta accettata .
Georgy,

-3

Questa è un'altra regex che ho imparato ad amare / odiare nell'ultima settimana, quindi di solito importa come (in questo caso sì) qualcosa che riflette come mi sento! fare una funzione normale .... chiedere input, quindi usare .... qualcosa = re.compile (r'foo * | spam * ', yes.I) ...... re.I (yes.I sotto) è lo stesso di IGNORECASE ma non puoi commettere tanti errori scrivendolo!

Quindi cerca il tuo messaggio usando regex's ma onestamente dovrebbe essere un paio di pagine a sé stante, ma il punto è che foo o spam vengono convogliati insieme e il caso viene ignorato. Quindi, se uno dei due viene trovato, lost_n_found visualizzerà uno di essi. se nessuno dei due, allora_n__n_found è uguale a Nessuno. Se non è uguale a nessuno restituisce user_input in minuscolo utilizzando "return lost_n_found.lower ()"

Ciò consente di abbinare molto più facilmente qualsiasi cosa sia sensibile al maiuscolo / minuscolo. Infine (NCS) sta per "nessuno si preoccupa seriamente ...!" o non distingue tra maiuscole e minuscole ....

se qualcuno ha qualche domanda, fammi capire

    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")
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.