Insensible à la casse 'in'


151

J'aime utiliser l'expression

if 'MICHAEL89' in USERNAMES:
    ...

USERNAMESest une liste.


Existe-t-il un moyen de faire correspondre les éléments avec insensibilité à la casse ou dois-je utiliser une méthode personnalisée? Je me demande simplement s'il est nécessaire d'écrire du code supplémentaire pour cela.

Réponses:


180
username = 'MICHAEL89'
if username.upper() in (name.upper() for name in USERNAMES):
    ...

Alternativement:

if username.upper() in map(str.upper, USERNAMES):
    ...

Ou, oui, vous pouvez créer une méthode personnalisée.


8
if 'CaseFudge'.lower() in [x.lower() for x in list]
fredley

44
[...]crée la liste entière. (name.upper() for name in USERNAMES)créerait seulement un générateur et une chaîne nécessaire à la fois - des économies de mémoire massives si vous faites beaucoup cette opération. (encore plus d'économies, si vous créez simplement une liste de noms d'utilisateurs en minuscules que vous réutilisez pour vérifier à chaque fois)
viraptor

2
Préférez abaisser toutes les clés lors de la construction du dict, pour des raisons de performances.
Ryan

1
si [x.lower () pour x dans la liste] est une compréhension de liste, est-ce que (name.upper () pour nom dans USERNAMES) est une compréhension de tuple? Ou a-t-il un autre nom?
otocan

1
@otocan C'est une expression génératrice.
nmichaels

21

Je ferais un emballage pour que vous puissiez être non invasif. Au minimum, par exemple ...:

class CaseInsensitively(object):
    def __init__(self, s):
        self.__s = s.lower()
    def __hash__(self):
        return hash(self.__s)
    def __eq__(self, other):
        # ensure proper comparison between instances of this class
        try:
           other = other.__s
        except (TypeError, AttributeError):
          try:
             other = other.lower()
          except:
             pass
        return self.__s == other

Maintenant, if CaseInsensitively('MICHAEL89') in whatever:devrait se comporter comme requis (que le côté droit soit une liste, un dict ou un ensemble). (Cela peut nécessiter plus d'efforts pour obtenir des résultats similaires pour l'inclusion de chaînes, éviter les avertissements dans certains cas impliquant unicode, etc.).


3
cela ne fonctionne pas pour dict essayez si CaseInsensitively ('MICHAEL89') in {'Michael89': True}: print "found"
Xavier Combelle

2
Xavier: Vous auriez besoin CaseInsensitively('MICHAEL89') in {CaseInsensitively('Michael89'):True}pour que cela fonctionne, ce qui ne relève probablement pas de "se comporter comme requis".
Gabe le

Voilà pour qu'il n'y ait qu'une seule façon évidente de le faire. Cela semble lourd à moins qu'il ne soit beaucoup utilisé. Cela dit, c'est très fluide.
nmichaels le

2
@Nathon, il me semble que devoir modifier de manière invasive le conteneur est l'opération "se sent lourd". Un emballage totalement non invasif: combien de "plus léger" que cela pourrait-on obtenir?! Pas tant;-). @Xavier, les RHS qui sont des dictionnaires ou des ensembles avec des clés / éléments à casse mixte ont besoin de leurs propres wrappers non invasifs (une partie des parties courtes etc.et "nécessitent plus d'effort" de ma réponse ;-).
Alex Martelli le

Ma définition du lourd implique l'écriture d'un peu de code pour créer quelque chose qui ne sera utilisé qu'une seule fois, alors qu'une version moins robuste mais beaucoup plus courte ferait l'affaire. Si cela doit être utilisé plus d'une fois, c'est parfaitement raisonnable.
nmichaels le

13

Habituellement (au moins en oop) vous façonnez votre objet pour qu'il se comporte comme vous le souhaitez. name in USERNAMESn'est pas insensible à la casse, il USERNAMESfaut donc changer:

class NameList(object):
    def __init__(self, names):
        self.names = names

    def __contains__(self, name): # implements `in`
        return name.lower() in (n.lower() for n in self.names)

    def add(self, name):
        self.names.append(name)

# now this works
usernames = NameList(USERNAMES)
print someone in usernames

La grande chose à ce sujet est que cela ouvre la voie à de nombreuses améliorations, sans avoir à changer de code en dehors de la classe. Par exemple, vous pouvez changer le self.namesen un ensemble pour des recherches plus rapides, ou calculer le (n.lower() for n in self.names)seul une fois et le stocker sur la classe et ainsi de suite ...


10

str.casefoldest recommandé pour la correspondance de chaînes insensible à la casse. La solution de @ nmichaels peut être facilement adaptée.

Utilisez soit:

if 'MICHAEL89'.casefold() in (name.casefold() for name in USERNAMES):

Ou:

if 'MICHAEL89'.casefold() in map(str.casefold, USERNAMES):

Selon les documents :

Le pliage de cas est similaire aux minuscules mais plus agressif car il est destiné à supprimer toutes les distinctions de casse dans une chaîne. Par exemple, la lettre minuscule allemande «ß» équivaut à «ss». Comme il est déjà minuscule, lower()ne ferait rien pour «ß»; casefold() le convertit en "ss".


8

Voici une façon:

if string1.lower() in string2.lower(): 
    ...

Pour que cela fonctionne, les objets string1et string2doivent être de type string.


5
AttributeError: l'objet 'list' n'a pas d'attribut 'lower'
Jeff

@Jeff c'est parce que l'un de vos éléments est une liste et que les deux objets doivent être une chaîne. Quel objet est une liste?
Utilisateur

1
Je vous voterais, mais je ne peux pas à moins que vous ne modifiiez votre réponse. Tu as tout à fait raison.
Jeff

@Jeff J'ai ajouté une clarification.
Utilisateur

6

Je pense que vous devez écrire du code supplémentaire. Par exemple:

if 'MICHAEL89' in map(lambda name: name.upper(), USERNAMES):
   ...

Dans ce cas, nous formons une nouvelle liste avec toutes les entrées USERNAMESconverties en majuscules, puis comparons avec cette nouvelle liste.

Mettre à jour

Comme le dit @viraptor , il est encore mieux d'utiliser un générateur au lieu de map. Voir la réponse de @Nathon .


Ou vous pouvez utiliser la itertoolsfonction imap. C'est beaucoup plus rapide qu'un générateur mais accomplit le même objectif.
Wheaties

5

Vous pourriez faire

matcher = re.compile('MICHAEL89', re.IGNORECASE)
filter(matcher.match, USERNAMES) 

Mise à jour: joué un peu et je pense que vous pourriez obtenir une meilleure approche de type court-circuit en utilisant

matcher = re.compile('MICHAEL89', re.IGNORECASE)
if any( ifilter( matcher.match, USERNAMES ) ):
    #your code here

La ifilterfonction provient d'itertools, l'un de mes modules préférés dans Python. Il est plus rapide qu'un générateur mais ne crée l'élément suivant de la liste que lorsqu'il est appelé.


Juste pour ajouter, le modèle pourrait avoir besoin d'être échappé, car il peut contenir des caractères comme ".", "?", Qui ont une signification spécifique dans les modèles d'expressions régulières. utilisez re.escape (raw_string) pour le faire
Iching Chang

0

Mes 5 (faux) cents

'a' dans "" .join (['A']). lower ()

METTRE À JOUR

Aïe, totalement d'accord @jpp, je vais garder comme exemple de mauvaise pratique :(


2
C'est faux. Considérez les 'a' in "".join(['AB']).lower()retours Truelorsque ce n'est pas ce que OP veut.
jpp

0

J'en avais besoin pour un dictionnaire au lieu d'une liste, la solution Jochen était la plus élégante pour ce cas, alors je l'ai un peu modifiée:

class CaseInsensitiveDict(dict):
    ''' requests special dicts are case insensitive when using the in operator,
     this implements a similar behaviour'''
    def __contains__(self, name): # implements `in`
        return name.casefold() in (n.casefold() for n in self.keys())

maintenant vous pouvez convertir un dictionnaire comme ça USERNAMESDICT = CaseInsensitiveDict(USERNAMESDICT)et utiliserif 'MICHAEL89' in USERNAMESDICT:


0

Pour l'avoir en une seule ligne, voici ce que j'ai fait:

if any(([True if 'MICHAEL89' in username.upper() else False for username in USERNAMES])):
    print('username exists in list')

Cependant, je ne l'ai pas testé avec le temps. Je ne sais pas à quel point c'est rapide / efficace.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.