'Insensible' en mayúsculas y minúsculas

151

Me encanta usar la expresión

if 'MICHAEL89' in USERNAMES:
    ...

donde USERNAMEShay una lista


¿Hay alguna manera de hacer coincidir los elementos con mayúsculas y minúsculas o necesito usar un método personalizado? Me pregunto si es necesario escribir código adicional para esto.

RadiantHex
fuente

Respuestas:

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

Alternativamente:

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

O, sí, puedes hacer un método personalizado.

nmichaels
fuente
8
if 'CaseFudge'.lower() in [x.lower() for x in list]
fredley
44
[...]crea la lista completa. (name.upper() for name in USERNAMES)crearía solo un generador y una cadena necesaria a la vez: un ahorro masivo de memoria si realiza muchas veces esta operación. (incluso más ahorros, si simplemente crea una lista de nombres de usuario en minúsculas que reutiliza para verificar cada vez)
viraptor
2
Prefiere bajar todas las claves al construir el dict, por razones de rendimiento.
Ryan
1
si [x.lower () para x en la lista] es una comprensión de la lista, ¿es (name.upper () para el nombre en USERNAMES) una comprensión de tupla? ¿O tiene otro nombre?
otocan
1
@otocan Es una expresión generadora.
nmichaels
21

Haría un envoltorio para que no seas invasivo. Mínimamente, por ejemplo ...:

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

Ahora, if CaseInsensitively('MICHAEL89') in whatever:debe comportarse como se requiere (ya sea que el lado derecho sea una lista, dict o set). (Puede requerir más esfuerzo lograr resultados similares para la inclusión de cadenas, evitar advertencias en algunos casos que involucren unicode, etc.).

Alex Martelli
fuente
3
eso no funciona para dict try si CaseInsensitively ('MICHAEL89') en {'Michael89': True}: print "found"
Xavier Combelle
2
Xavier: Necesitarías CaseInsensitively('MICHAEL89') in {CaseInsensitively('Michael89'):True}que eso funcione, lo que probablemente no se clasifique como "comportarse como se requiere".
Gabe
Esto en cuanto a que solo hay una forma obvia de hacerlo. Esto se siente pesado a menos que se vaya a usar mucho. Dicho eso, es muy suave.
nmichaels
2
@Nathon, me parece que tener que alterar invasivamente el contenedor es la operación "se siente pesada". Una envoltura completamente no invasiva: ¿cuánto más "ligero" que esto podría ser? No mucho;-). @Xavier, los RHS que son dictados o conjuntos con teclas / elementos de mayúsculas y minúsculas necesitan sus propios envoltorios no invasivos (parte de las partes cortas etc.y "requieren más esfuerzo" de mi respuesta ;-).
Alex Martelli
Mi definición de pesado implica escribir bastante código para hacer algo que solo se usará una vez, donde una versión menos robusta pero mucho más corta sería suficiente. Si esto se va a usar más de una vez, es perfectamente sensato.
nmichaels
12

Por lo general (en al menos al menos) le das forma a tu objeto para que se comporte de la manera que quieras. name in USERNAMESno distingue entre mayúsculas y minúsculas, por lo que USERNAMESdebe cambiar:

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

Lo mejor de esto es que abre el camino para muchas mejoras, sin tener que cambiar ningún código fuera de la clase. Por ejemplo, podría cambiarlo self.namesa un conjunto para búsquedas más rápidas, o calcular la (n.lower() for n in self.names)única vez y almacenarlo en la clase, etc.

Jochen Ritzel
fuente
10

str.casefoldse recomienda para la coincidencia de cadenas entre mayúsculas y minúsculas. La solución de @nmichaels se puede adaptar trivialmente.

Utilizar cualquiera:

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

O:

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

Según los documentos :

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. Por ejemplo, la letra minúscula alemana 'ß' es equivalente a "ss". Como ya está en minúsculas, lower()no haría nada a 'ß'; casefold() lo convierte a "ss".

jpp
fuente
8

Aquí hay una manera:

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

Para que esto funcione, ambos string1y los string2objetos deben ser de tipo string.

Usuario
fuente
55
AttributeError: el objeto 'list' no tiene atributo 'lower'
Jeff el
@Jeff es porque uno de tus elementos es una lista, y ambos objetos deben ser una cadena. ¿Qué objeto es una lista?
Usuario
1
Te votaría, pero no puedo a menos que edites tu respuesta. Tienes toda la razón.
Jeff
@ Jeff agregué una aclaración.
Usuario
6

Creo que tienes que escribir un código extra. Por ejemplo:

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

En este caso, estamos formando una nueva lista con todas las entradas en USERNAMES convertidas a mayúsculas y luego comparándolas con esta nueva lista.

Actualizar

Como dice @viraptor , es aún mejor usar un generador en lugar de map. Ver la respuesta de @Nathon .

Manoj Govindan
fuente
O podrías usar la itertoolsfunción imap. Es mucho más rápido que un generador pero logra el mismo objetivo.
wheaties
5

Podrías hacerlo

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

Actualización: jugué un poco y estoy pensando que podría obtener un mejor enfoque de tipo de cortocircuito utilizando

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

La ifilterfunción es de itertools, uno de mis módulos favoritos dentro de Python. Es más rápido que un generador, pero solo crea el siguiente elemento de la lista cuando se le solicita.

Wheaties
fuente
Solo para agregar, el patrón podría necesitar escapar, ya que podría contener caracteres como ".", "?", Que tiene un significado especial en los patrones de expresión regular. use re.escape (raw_string) para hacerlo
Iching Chang
0

Mis 5 centavos (incorrectos)

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

ACTUALIZAR

Ay, totalmente de acuerdo @jpp, lo guardaré como un ejemplo de mala práctica :(

GBrian
fuente
2
Esto está mal. Considere 'a' in "".join(['AB']).lower()devoluciones Truecuando esto no es lo que OP quiere.
jpp
0

Necesitaba esto para un diccionario en lugar de una lista, la solución Jochen fue la más elegante para ese caso, así que la modifiqué un poco:

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

ahora puedes convertir un diccionario así USERNAMESDICT = CaseInsensitiveDict(USERNAMESDICT)y usarif 'MICHAEL89' in USERNAMESDICT:

Megarushing
fuente
0

Para tenerlo en una línea, esto es lo que hice:

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

Sin embargo, no lo probé en el tiempo. No estoy seguro de lo rápido / eficiente que es.

AMF
fuente