Comment puis-je faire une comparaison de chaînes insensible à la casse en Python?
Je voudrais encapsuler la comparaison d'une chaîne normale à une chaîne de référentiel d'une manière très simple et pythonique. Je voudrais aussi avoir la possibilité de rechercher des valeurs dans un dict haché par des chaînes en utilisant des chaînes python normales.
En supposant que ASCII chaînes:
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)")
Comparer une chaîne sans tenir compte de la casse semble une tâche triviale, mais ce n'est pas le cas. Je vais utiliser Python 3, car Python 2 est sous-développé ici.
La première chose à noter est que les conversions avec suppression de casse en Unicode ne sont pas triviales. Il y a du texte pour lequel text.lower() != text.upper().lower()
, tel que "ß"
:
"ß".lower()
#>>> 'ß'
"ß".upper().lower()
#>>> 'ss'
Mais supposons que vous vouliez comparer "BUSSE"
et "Buße"
sans faille. Heck, vous voulez probablement aussi comparer "BUSSE"
et "BUẞE"
égaux - c'est la nouvelle forme de capital. La méthode recommandée consiste à utiliser casefold
:
help(str.casefold)
#>>> Help on method_descriptor:
#>>>
#>>> casefold(...)
#>>> S.casefold() -> str
#>>>
#>>> Return a version of S suitable for caseless comparisons.
#>>>
Ne vous contentez pas d’utiliser lower
. Si casefold
n'est pas disponible, exécuter .upper().lower()
aide (mais seulement un peu).
Ensuite, vous devriez considérer les accents. Si votre rendu de police est bon, vous pensez probablement "ê" == "ê"
- mais ce n'est pas le cas:
"ê" == "ê"
#>>> False
C'est parce qu'ils sont réellement
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']
Le moyen le plus simple de gérer ceci est unicodedata.normalize
. Vous voudrez probablement utiliser la normalisation NFKD, mais n'hésitez pas à consulter la documentation. Alors on fait
unicodedata.normalize("NFKD", "ê") == unicodedata.normalize("NFKD", "ê")
#>>> True
Pour finir, voici ce qui s’exprime en fonctions:
import unicodedata
def normalize_caseless(text):
return unicodedata.normalize("NFKD", text.casefold())
def caseless_equal(left, right):
return normalize_caseless(left) == normalize_caseless(right)
En utilisant Python 2, appelez .lower()
sur chaque chaîne ou objet Unicode ...
string1.lower() == string2.lower()
... fonctionnera la plupart du temps, mais ne fonctionne pas dans les situations décrites par @tchrist .
Supposons que nous ayons un fichier appelé unicode.txt
contenant les deux chaînes Σίσυφος
et ΣΊΣΥΦΟΣ
. Avec 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
Le caractère Σ a deux formes minuscules, ς et σ, et .lower()
ne vous aidera pas à les comparer sans distinction de casse.
Cependant, à partir de Python 3, les trois formes seront résolues en ς, et l'appel de lower () sur les deux chaînes fonctionnera correctement:
>>> 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
Donc, si vous vous souciez des cas Edge comme les trois sigmas en grec, utilisez Python 3.
(Pour référence, Python 2.7.3 et Python 3.3.0b1 sont indiqués dans les impressions d'interprète ci-dessus.)
Section 3.13 de la norme Unicode définit des algorithmes pour la correspondance sans cas.
X.casefold() == Y.casefold()
in Python 3 implémente la "correspondance sans cas par défaut" (D144).
Le casage ne préserve pas la normalisation des chaînes dans toutes les instances et la normalisation doit donc être effectuée ('å'
vs. 'å'
). Le D145 introduit la "correspondance canonique sans cas":
import unicodedata
def NFD(text):
return unicodedata.normalize('NFD', text)
def canonical_caseless(text):
return NFD(NFD(text).casefold())
NFD()
est appelée deux fois pour les très rares cas Edge impliquant le caractère U + 0345.
Exemple:
>>> 'å'.casefold() == 'å'.casefold()
False
>>> canonical_caseless('å') == canonical_caseless('å')
True
Il existe également des correspondances de compatibilité sans cas (D146) pour des cas tels que '㎒'
(U + 3392) et la "correspondance sans identifiant" pour simplifier et optimiser correspondance des identifiants sans cas .
J'ai vu cette solution ici en utilisant regex .
import re
if re.search('mandy', 'Mandy Pande', re.IGNORECASE):
# is True
Ça marche bien avec les accents
In [42]: if re.search("ê","ê", re.IGNORECASE):
....: print(1)
....:
1
Cependant, cela ne fonctionne pas avec les caractères unicode insensibles à la casse. Merci à @Rhymoid d’avoir signalé que, comme j’ai bien compris, il faut le symbole exact pour que le cas soit vrai. La sortie est la suivante:
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)
....:
Que diriez-vous de convertir en minuscule en premier? vous pouvez utiliser string.lower()
.
L'approche habituelle consiste à mettre les chaînes en majuscules ou en minuscules pour les recherches et les comparaisons. Par exemple:
>>> "hello".upper() == "HELLO".upper()
True
>>>
C’est une autre expression rationnelle que j’ai appris à aimer/à haïr au cours de la semaine écoulée et qui importe donc habituellement (dans ce cas oui) quelque chose qui reflète à quel point je me sens! faire une fonction normale .... demander une entrée, puis utiliser .... quelque chose = re.compile (r'foo * | spam * ', yes.I) ...... re.I (yes.I ci-dessous) est identique à IGNORECASE mais vous ne pouvez pas commettre autant d’erreurs en l’écrivant!
Vous recherchez ensuite votre message en utilisant regex, mais honnêtement, il devrait contenir quelques pages, mais le fait est que foo ou spam sont rassemblés et que la casse est ignorée. Ensuite, si l'un ou l'autre est trouvé, lost_n_found en affichera un. si ni alors lost_n_found est égal à Aucun. Si ce n'est pas égal à none, retourne user_input en minuscule avec "return lost_n_found.lower ()"
Cela vous permet de faire correspondre beaucoup plus facilement tout ce qui va être sensible à la casse. Enfin, NCS signifie "personne ne se soucie sérieusement ...!" ou pas sensible à la casse .... selon
si quelqu'un a des questions me faire sur ce ..
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")