'In' não diferencia maiúsculas de minúsculas

150

Eu amo usar a expressão

if 'MICHAEL89' in USERNAMES:
    ...

onde USERNAMESestá uma lista


Existe alguma maneira de corresponder itens com distinção entre maiúsculas e minúsculas ou preciso usar um método personalizado? Basta saber se é necessário escrever um código extra para isso.

RadiantHex
fonte

Respostas:

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

Alternativamente:

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

Ou, sim, você pode criar um método personalizado.

nmichaels
fonte
8
if 'CaseFudge'.lower() in [x.lower() for x in list]
fredley
44
[...]cria a lista inteira. (name.upper() for name in USERNAMES)criaria apenas um gerador e uma string necessária de cada vez - economia de memória massiva se você estiver fazendo muito esta operação. (poupança ainda mais, se você simplesmente criar uma lista de nomes de usuários minúsculas que você reutilizar para a verificação de cada vez)
viraptor
2
Prefira abaixar todas as chaves ao criar o dict, por razões de desempenho.
Ryan
1
se [x.lower () para x na lista] é uma compreensão da lista, é (name.upper () para o nome em USERNAMES) uma compreensão da tupla? Ou tem outro nome?
Otocan 19/0418
1
@otocan É uma expressão geradora.
Nmichaels
21

Eu faria um invólucro para que você não seja invasivo. Minimamente, por exemplo ...:

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

Agora, if CaseInsensitively('MICHAEL89') in whatever:deve se comportar conforme necessário (se o lado direito é uma lista, ditado ou conjunto). (Pode ser necessário mais esforço para obter resultados semelhantes para a inclusão de strings, evitar avisos em alguns casos envolvendo unicodeetc.).

Alex Martelli
fonte
3
que não funciona para dict try if CaseInsensitively ('MICHAEL89') em {'Michael89': True}: print "found"
Xavier Combelle
2
Xavier: Você precisaria CaseInsensitively('MICHAEL89') in {CaseInsensitively('Michael89'):True}que isso funcionasse, o que provavelmente não se enquadra em "se comporta conforme necessário".
Gabe
Tanto por haver apenas uma maneira óbvia de fazê-lo. Parece pesado, a menos que seja usado muito. Dito isto, é muito suave.
Nmichaels 02/09/10
2
@ Nathon, parece-me que ter que alterar invasivamente o contêiner é a operação "parece pesada". Um invólucro completamente não invasivo: quanto mais "leve" que isso poderia ser obtido ?! Não muito;-). @Xavier, os RHS que são dictos ou conjuntos com chaves / itens de maiúsculas e minúsculas precisam de seus próprios invólucros não invasivos (parte do curto etc.e partes "exigem mais esforço" da minha resposta ;-).
Alex Martelli
Minha definição de heavy envolve escrever um pouco de código para criar algo que só será usado uma vez, onde uma versão menos robusta, mas muito mais curta faria. Se isso for usado mais de uma vez, é perfeitamente sensato.
Nmichaels 02/09/10
12

Normalmente (pelo menos no topo), você molda seu objeto para se comportar da maneira que deseja. name in USERNAMESnão faz distinção entre maiúsculas e minúsculas, por isso USERNAMESprecisa mudar:

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

O melhor disso é que ele abre o caminho para muitas melhorias, sem precisar alterar nenhum código fora da classe. Por exemplo, você pode alterar self.namespara um conjunto para pesquisas mais rápidas ou calcular a (n.lower() for n in self.names)única vez e armazená-lo na classe e assim por diante ...

Jochen Ritzel
fonte
10

str.casefoldé recomendado para correspondência de cadeia que não diferencia maiúsculas de minúsculas. A solução do @ nmichaels pode ser trivialmente adaptada.

Use:

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

Ou:

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

Conforme os documentos :

A dobragem de caixa é semelhante à minúscula, mas é mais agressiva porque se destina a remover todas as distinções de maiúsculas e minúsculas em uma sequência. Por exemplo, a letra minúscula alemã 'ß' é equivalente a "ss". Como já está em minúscula,lower() não faria nada para 'ß'; casefold() converte para "ss".

jpp
fonte
8

Aqui está uma maneira:

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

Para que isso funcione, os objetos string1e string2devem ser do tipo string.

Do utilizador
fonte
5
AttributeError: o objeto 'list' não tem nenhum atributo 'lower' #
1174 Jeff Jeff
@ Jeff, é porque um dos seus elementos é uma lista e os dois objetos devem ser uma string. Qual objeto é uma lista?
Utilizador
1
Eu votaria em você, mas não posso, a menos que você edite sua resposta. Você está absolutamente certo.
Jeff
@ Jeff Adicionei esclarecimentos.
Utilizador
6

Eu acho que você precisa escrever um código extra. Por exemplo:

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

Nesse caso, estamos formando uma nova lista com todas as entradas em USERNAMES convertidas para maiúsculas e comparando com essa nova lista.

Atualizar

Como o @viraptor diz, é ainda melhor usar um gerador em vez de map. Veja a resposta da @Nathon .

Manoj Govindan
fonte
Ou você pode usar a itertoolsfunção imap. É muito mais rápido que um gerador, mas cumpre o mesmo objetivo.
wheaties
5

Você poderia fazer

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

Atualização: joguei um pouco e estou pensando que você poderia obter uma melhor abordagem do tipo de curto-circuito usando

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

A ifilterfunção é do itertools, um dos meus módulos favoritos no Python. É mais rápido que um gerador, mas apenas cria o próximo item da lista quando chamado.

wheaties
fonte
Apenas para adicionar, o padrão pode precisar ser escapado, pois pode conter caracteres como ".", "?", Que tem significado específico nos padrões de expressão regular. uso re.escape (raw_string) para fazê-lo
Iching Chang
0

Meus 5 centavos (errados)

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

ATUALIZAR

Ouch, concordo totalmente @jpp, vou manter como um exemplo de má prática :(

GBrian
fonte
2
Isto está errado. Considere 'a' in "".join(['AB']).lower()retornos Truequando isso não for o que o OP deseja.
jpp
0

Eu precisava disso para um dicionário em vez da lista, a solução Jochen era a mais elegante para esse caso, então modifiquei um pouco:

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

agora você pode converter um dicionário assim USERNAMESDICT = CaseInsensitiveDict(USERNAMESDICT)e usarif 'MICHAEL89' in USERNAMESDICT:

Megarushing
fonte
0

Para colocá-lo em uma linha, foi o que eu fiz:

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

Eu não testei no tempo, no entanto. Não tenho certeza de quão rápido / eficiente é.

MFA
fonte