it-swarm-pt.tech

Ignorar maiúsculas e minúsculas em strings Python

Qual é a maneira mais fácil de comparar strings no Python, ignorando o caso?

Claro que se pode fazer (str1.lower () <= str2.lower ()), etc., mas isso criou duas strings temporárias adicionais (com as óbvias sobrecargas alloc/g-c).

Eu acho que estou procurando por um equivalente ao stricmp () do C.

[Algum contexto mais solicitado, então vou demonstrar com um exemplo trivial:]

Suponha que você queira ordenar uma lista de strings. Você simplesmente faz oList.sort (). Isto é comparações de strings O (n * log (n)) e nenhum gerenciamento de memória (já que todos os strings e elementos de lista São algum tipo de ponteiros inteligentes) . Você está feliz.

Agora, você quer fazer o mesmo, mas ignorar o caso (vamos simplificar e dizer Todas as strings são ascii, então os problemas de localidade podem ser ignorados). Você pode fazer oList.sort (key = lambda s: s.lower ()), mas você causa duas novas alocações por comparação, mais sobrecarrega o coletor de lixo com as cadeias duplicadas (abaixadas). Cada um desses ruídos de gerenciamento de memória é de magnitude de ordens mais lento que a simples comparação de strings.

Agora, com uma função no local stricmp (), você faz: theList.sort (cmp = stricmp) E é tão rápido e tão fácil quanto a memória do que theList.sort (). Você está feliz de novo.

O problema é que qualquer comparação insensível a maiúsculas baseada em Python envolve duplicações implícitas de cadeias de caracteres , Então eu esperava encontrar uma comparação baseada em C (talvez em uma cadeia de módulos).

Não foi possível encontrar algo assim, daí a questão aqui. (Espero que isso esclarece a questão).

51
Paul Oyster

Aqui está um benchmark mostrando que usar str.lower é mais rápido que o método proposto da resposta aceita (libc.strcasecmp):

#!/usr/bin/env python2.7
import random
import timeit

from ctypes import *
libc = CDLL('libc.dylib') # change to 'libc.so.6' on linux

with open('/usr/share/dict/words', 'r') as wordlist:
    words = wordlist.read().splitlines()
random.shuffle(words)
print '%i words in list' % len(words)

setup = 'from __main__ import words, libc; gc.enable()'
stmts = [
    ('simple sort', 'sorted(words)'),
    ('sort with key=str.lower', 'sorted(words, key=str.lower)'),
    ('sort with cmp=libc.strcasecmp', 'sorted(words, cmp=libc.strcasecmp)'),
]

for (comment, stmt) in stmts:
    t = timeit.Timer(stmt=stmt, setup=setup)
    print '%s: %.2f msec/pass' % (comment, (1000*t.timeit(10)/10))

tempos típicos na minha máquina:

235886 words in list
simple sort: 483.59 msec/pass
sort with key=str.lower: 1064.70 msec/pass
sort with cmp=libc.strcasecmp: 5487.86 msec/pass

Então, a versão com str.lower não é apenas a mais rápida de longe, mas também a mais portátil e Python de todas as soluções propostas aqui. Eu não especifiquei o uso da memória, mas o pôster original ainda não deu motivo para se preocupar com isso. Além disso, quem diz que uma chamada para o módulo libc não duplica as strings?

NB: O método string lower() também tem a vantagem de ser dependente de localidade. Algo que você provavelmente não estará acertando ao escrever sua própria solução "otimizada". Mesmo assim, devido a bugs e recursos ausentes no Python, esse tipo de comparação pode gerar resultados errados em um contexto unicode.

74
user3850

Você está usando essa comparação em um caminho executado com muita frequência em um aplicativo altamente sensível ao desempenho? Como alternativa, você está executando isso em seqüências de caracteres que são megabytes em tamanho? Caso contrário, você não deve se preocupar com o desempenho e apenas usar o método .lower ().

O código a seguir demonstra que fazer uma comparação não diferencia maiúsculas de minúsculas chamando .lower () em duas cadeias de caracteres que são cada um quase um megabyte de tamanho leva cerca de 0,009 segundos no meu computador de mesa 1.8 GHz:

from timeit import Timer

s1 = "1234567890" * 100000 + "a"
s2 = "1234567890" * 100000 + "B"

code = "s1.lower() < s2.lower()"
time = Timer(code, "from __main__ import s1, s2").timeit(1000)
print time / 1000   # 0.00920499992371 on my machine

Se, de fato, essa é uma seção de código extremamente importante e crítica ao desempenho, recomendo escrever uma função em C e chamá-la de seu código Python, pois isso permitirá que você faça uma pesquisa realmente eficiente, sem distinção entre maiúsculas e minúsculas. Detalhes sobre como escrever módulos de extensão C podem ser encontrados aqui: https://docs.python.org/extending/extending.html

7
Eli Courtwright

Sua pergunta implica que você não precisa de Unicode. Experimente o seguinte trecho de código; se funciona para você, está feito:

Python 2.5.2 (r252:60911, Aug 22 2008, 02:34:17)
[GCC 4.3.1] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import locale
>>> locale.setlocale(locale.LC_COLLATE, "en_US")
'en_US'
>>> sorted("ABCabc", key=locale.strxfrm)
['a', 'A', 'b', 'B', 'c', 'C']
>>> sorted("ABCabc", cmp=locale.strcoll)
['a', 'A', 'b', 'B', 'c', 'C']

Esclarecimento: caso não seja óbvio à primeira vista, o locale.strcoll parece ser a função que você precisa, evitando as strings "duplicadas" str.lower ou locale.strxfrm.

7
tzot

Não consigo encontrar nenhuma outra maneira integrada de fazer comparação insensível a maiúsculas e minúsculas: A receita de livro de receitas python usa lower ().

No entanto, você tem que ter cuidado ao usar menor para comparações por causa do problema turco I . Infelizmente, a manipulação do Python para o turco Is não é boa. I é convertido em I, mas não é convertido em ı. © é convertido em i, mas não é convertido para İ. 

5
Douglas Leeder

Não há construído em equivalente a essa função que você deseja.

Você pode escrever sua própria função que converte em .lower () cada caractere de cada vez para evitar a duplicação de ambas as strings, mas tenho certeza que será muito intensivo em CPU e extremamente ineficiente. 

A menos que você esteja trabalhando com strings extremamente longas (tanto tempo que podem causar um problema de memória se duplicado), então eu manteria simples e usaria 

str1.lower() == str2.lower()

Você ficará bem

3
Ricardo Reyes

Esta questão está perguntando 2 coisas muito diferentes:

  1. Qual é a maneira mais fácil de comparar strings no Python, ignorando o caso?
  2. Eu acho que estou procurando por um equivalente ao stricmp () do C.

Desde que o # 1 foi respondido muito bem já (isto é: str1.lower () <str2.lower ()) eu responderei # 2.

def strincmp(str1, str2, numchars=None):
    result = 0
    len1 = len(str1)
    len2 = len(str2)
    if numchars is not None:
        minlen = min(len1,len2,numchars)
    else:
        minlen = min(len1,len2)
    #end if
    orda = ord('a')
    ordz = ord('z')

    i = 0
    while i < minlen and 0 == result:
        ord1 = ord(str1[i])
        ord2 = ord(str2[i])
        if ord1 >= orda and ord1 <= ordz:
            ord1 = ord1-32
        #end if
        if ord2 >= orda and ord2 <= ordz:
            ord2 = ord2-32
        #end if
        result = cmp(ord1, ord2)
        i += 1
    #end while

    if 0 == result and minlen != numchars:
        if len1 < len2:
            result = -1
        Elif len2 < len1:
            result = 1
        #end if
    #end if

    return result
#end def

Use essa função apenas quando fizer sentido, pois, em muitos casos, a técnica de letras minúsculas será superior.

Eu só trabalho com strings ascii, não sei como isso vai se comportar com unicode.

2
trevorcroft

Quando algo não é bem suportado na biblioteca padrão, eu sempre procuro por um pacote PyPI. Com a virtualização e a onipresença das distribuições modernas do Linux, não evito mais as extensões do Python. PyICU parece se encaixar no projeto: https://stackoverflow.com/a/1098160/3461

Existe agora também uma opção que é pura python. Está bem testado: https://github.com/jtauber/pyuca


Resposta antiga:

Eu gosto da solução de expressão regular. Aqui está uma função que você pode copiar e colar em qualquer função, graças ao suporte à estrutura de blocos do Python.

def equals_ignore_case(str1, str2):
    import re
    return re.match(re.escape(str1) + r'\Z', str2, re.I) is not None

Como usei correspondência em vez de pesquisa, não precisei adicionar um circunflexo (^) à expressão regular.

Nota: Isso só verifica a igualdade, que às vezes é o que é necessário. Eu também não iria tão longe a ponto de dizer que gosto disso.

2
Benjamin Atkin

O idioma recomendado para classificar listas de valores usando chaves caras para calcular é o chamado "padrão decorado". Consiste simplesmente em construir uma lista de tuplas (chave, valor) da lista original e ordenar essa lista. Então é trivial eliminar as chaves e obter a lista de valores classificados:

>>> original_list = ['a', 'b', 'A', 'B']
>>> decorated = [(s.lower(), s) for s in original_list]
>>> decorated.sort()
>>> sorted_list = [s[1] for s in decorated]
>>> sorted_list
['A', 'a', 'B', 'b']

Ou se você gosta de one-liners:

>>> sorted_list = [s[1] for s in sorted((s.lower(), s) for s in original_list)]
>>> sorted_list
['A', 'a', 'B', 'b']

Se você realmente se preocupar com o custo de chamar lower (), você pode apenas armazenar tuplas de (string baixada, string original) em todos os lugares. As tuplas são o tipo mais barato de contêineres em Python, elas também são hasháveis ​​para que possam ser usadas como chaves de dicionário, membros do conjunto, etc.

1
Antoine P.

É assim que você faria com re:

import re
p = re.compile('^hello$', re.I)
p.match('Hello')
p.match('hello')
p.match('HELLO')
1
Moses Ting

Para comparações ocasionais ou até mesmo repetidas, alguns objetos de string extras não devem importar, desde que isso não aconteça no loop mais interno de seu código principal ou você não tenha dados suficientes para realmente notar o impacto no desempenho. Veja se você faz: fazer as coisas de uma maneira "estúpida" é muito menos estúpido se você também fizer menos.

Se você quiser seriamente continuar comparando lotes de texto insensitivo a maiúsculas e minúsculas, poderia de alguma forma manter as versões em minúsculas das strings à mão para evitar a finalização e recriação, ou normalizar todo o conjunto de dados em minúsculas. Isso obviamente depende do tamanho do conjunto de dados. Se houver relativamente poucas agulhas e um grande palheiro, a substituição das agulhas por objetos regexp compilados é uma solução. Se é difícil dizer sem ver um exemplo concreto.

0
yason

Você poderia traduzir cada string para minúscula uma vez --- preguiçosamente somente quando precisar, ou como um prepass para o tipo, se você sabe que estará classificando toda a coleção de strings. Existem várias maneiras de anexar essa chave de comparação aos dados reais que estão sendo classificados, mas essas técnicas devem ser tratadas em um problema separado.

Observe que essa técnica pode ser usada não apenas para lidar com problemas de letras maiúsculas/minúsculas, mas também para outros tipos de classificação, como classificação específica de localidade ou classificação de título "estilo Biblioteca" que ignora os principais artigos e normaliza os dados antes de classificá-los.

0
Dale Wilson

Basta usar o método str().lower(), a menos que o alto desempenho seja importante - nesse caso, escreva esse método de classificação como uma extensão C.

"Como escrever uma extensão Python" parece ser uma introdução decente.

Mais interessante, Este guia compara o uso da biblioteca ctypes vs escrever um módulo C externo (o ctype é substancialmente mais lento que a extensão C).

0
dbr
import re
if re.match('tEXT', 'text', re.IGNORECASE):
    # is True
0
Venkatesh Bachu

Tenho certeza que você tem que usar .lower () ou usar uma expressão regular. Eu não estou ciente de uma função de comparação de seqüência de caracteres insensível a maiúsculas e minúsculas.

0
Mark Biek