Retirando tudo, exceto caracteres alfanuméricos, de uma sequência em Python

337

Qual é a melhor maneira de remover todos os caracteres não alfanuméricos de uma string, usando Python?

As soluções apresentadas na variante PHP desta pergunta provavelmente funcionarão com alguns pequenos ajustes, mas não me parecem muito 'pitônicas'.

Para o registro, eu não quero apenas retirar períodos e vírgulas (e outras pontuações), mas também aspas, colchetes, etc.

Mark van Lent
fonte
7
Você se importa com caracteres alfanuméricos internacionais, como 'æøå', 'مرحبا', 'สวัสดี', 'こ ん に ち は'?
Pimin Konstantin Kefaloukos
4
@PiminKonstantinKefaloukos Sim, eu me importo com os caracteres internacionais, portanto, meu comentário sobre a resposta aceita para usar o re.UNICODE.
Mark van Lent

Respostas:

336

Eu apenas cronometrei algumas funções por curiosidade. Nestes testes, estou removendo caracteres não alfanuméricos da string string.printable(parte do stringmódulo interno). O uso de compilado '[\W_]+'e pattern.sub('', str)foi encontrado para ser o mais rápido.

$ python -m timeit -s \
     "import string" \
     "''.join(ch for ch in string.printable if ch.isalnum())" 
10000 loops, best of 3: 57.6 usec per loop

$ python -m timeit -s \
    "import string" \
    "filter(str.isalnum, string.printable)"                 
10000 loops, best of 3: 37.9 usec per loop

$ python -m timeit -s \
    "import re, string" \
    "re.sub('[\W_]', '', string.printable)"
10000 loops, best of 3: 27.5 usec per loop

$ python -m timeit -s \
    "import re, string" \
    "re.sub('[\W_]+', '', string.printable)"                
100000 loops, best of 3: 15 usec per loop

$ python -m timeit -s \
    "import re, string; pattern = re.compile('[\W_]+')" \
    "pattern.sub('', string.printable)" 
100000 loops, best of 3: 11.2 usec per loop
Otto Allmendinger
fonte
2
Resultados muito interessantes: eu esperava que as expressões regulares fossem mais lentas. Curiosamente, eu tentei isso com uma outra opção ( valid_characters = string.ascii_letters + string.digitsseguido por join(ch for ch in string.printable if ch in valid_characters)e foi 6 microssegundos mais rápido do que a isalnum()opção ainda muito mais lento do que o regexp embora..
Dral
+1, medir o tempo é bom! (mas no penúltimo, faça isso pattern.sub('', string.printable)- bobagem chamar re.sub quando você tiver um objeto RE! -).
287 Alex Martelli
46
Para o registro: use re.compile('[\W_]+', re.UNICODE)para torná-lo seguro para Unicode.
Mark van Lent
3
como você faz isso sem remover o espaço em branco?
Maudulus
6
faça isso sem remover o espaço em branco: re.sub ('[\ W _] +', '', sentença, sinalizadores = re.UNICODE)
PALEN
267

Expressões regulares para o resgate:

import re
re.sub(r'\W+', '', your_string)

Por definição Python '\W== [^a-zA-Z0-9_], que exclui tudo numbers, letterse_

Formigas Aasma
fonte
2
O que o sinal de mais faz na regexp? (Eu sei o que significa, apenas curioso para saber por que é necessário para o re.sub.)
Mark van Lent
7
@ Mark: Eu imagino que isso aceleraria a substituição, pois a substituição se livraria de todos os caracteres não-palavras em um bloco de uma só vez, em vez de removê-los um a um.
Dral
2
Sim, apoiei isso enquanto ajustava algum código crítico de desempenho há um tempo atrás. Se houver uma quantidade significativa de caracteres para substituir, a aceleração é enorme.
Ants Aasma
20
Pode não ser relevante neste caso, mas \Wtambém manterá sublinhados.
Blixt 14/08/09
12
Seguinte dica @Blixt, se só deseja letras e números que você pode fazer re.sub (r '[^ a-zA-Z0-9]', '', your_string)
Nigini
68

Use o método str.translate () .

Presumindo que você faça isso com frequência:

(1) Crie uma sequência contendo todos os caracteres que você deseja excluir:

delchars = ''.join(c for c in map(chr, range(256)) if not c.isalnum())

(2) Sempre que você deseja torcer uma corda:

scrunched = s.translate(None, delchars)

O custo da instalação provavelmente se compara favoravelmente ao re.compile; o custo marginal é bem menor:

C:\junk>\python26\python -mtimeit -s"import string;d=''.join(c for c in map(chr,range(256)) if not c.isalnum());s=string.printable" "s.translate(None,d)"
100000 loops, best of 3: 2.04 usec per loop

C:\junk>\python26\python -mtimeit -s"import re,string;s=string.printable;r=re.compile(r'[\W_]+')" "r.sub('',s)"
100000 loops, best of 3: 7.34 usec per loop

Nota: Usar string.printable como dados de referência fornece ao padrão '[\ W _] +' uma vantagem injusta ; todos os caracteres não alfanuméricos estão em um grupo ... em dados típicos, haveria mais de uma substituição a ser feita:

C:\junk>\python26\python -c "import string; s = string.printable; print len(s),repr(s)"
100 '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;=>?@[\\]^_`{|}~ \t\n\r\x0b\x0c'

Aqui está o que acontece se você der ao re.sub um pouco mais de trabalho a fazer:

C:\junk>\python26\python -mtimeit -s"d=''.join(c for c in map(chr,range(256)) if not c.isalnum());s='foo-'*25" "s.translate(None,d)"
1000000 loops, best of 3: 1.97 usec per loop

C:\junk>\python26\python -mtimeit -s"import re;s='foo-'*25;r=re.compile(r'[\W_]+')" "r.sub('',s)"
10000 loops, best of 3: 26.4 usec per loop
John Machin
fonte
11
O uso do translate é realmente um pouco mais rápido. Mesmo ao adicionar um loop for imediatamente antes de fazer a substituição / tradução (para reduzir os custos de instalação), a tradução é aproximadamente 17 vezes mais rápida que a regexp na minha máquina. Bom saber.
Mark van Lent
3
Esta é definitivamente a solução mais pitônica.
Codygman
11
Isso quase me convencer, mas gostaria de sugerir utilizando string.punctuationEm vez de''.join(c for c in map(chr, range(256)) if not c.isalnum())
ArnauOrriols
11
Observe que isso funciona para strobjetos, mas não para unicodeobjetos.
Yavar
@ John Machin Isso é essencialmente uma compreensão da lista que está sendo passada como argumento .join()?
AdjunctProfessorFalcon
41

Você poderia tentar:

print ''.join(ch for ch in some_string if ch.isalnum())
ars
fonte
15
>>> import re
>>> string = "Kl13@£$%[};'\""
>>> pattern = re.compile('\W')
>>> string = re.sub(pattern, '', string)
>>> print string
Kl13
DeslocadoAussie
fonte
eu amei a sua resposta, mas ele remove os caracteres árabes também você pode me dizer como mantê-los
Charif DZ
13

E se:

def ExtractAlphanumeric(InputString):
    from string import ascii_letters, digits
    return "".join([ch for ch in InputString if ch in (ascii_letters + digits)])

Isso funciona usando a compreensão de lista para produzir uma lista dos caracteres, InputStringse eles estiverem presentes nas combinações ascii_letterse digitsseqüências de caracteres. Em seguida, junta a lista em uma sequência.

DrAl
fonte
Parece que string.ascii_letters contém apenas letras (duh) e não números. Eu também preciso dos números ...
Mark van Lent
Adicionar string.digits realmente resolveria o problema que acabei de mencionar. :)
Mark van Lent
Sim, percebi isso quando voltei a ler sua pergunta. Nota para si mesmo: aprenda a ler!
DrAl
4

Como resultado de algumas outras respostas aqui, ofereço uma maneira realmente simples e flexível de definir um conjunto de caracteres aos quais você deseja limitar o conteúdo de uma string. Nesse caso, estou permitindo sublinhar e trace alfanumérico PLUS. Basta adicionar ou remover caracteres do meu PERMITTED_CHARSconforme o seu caso de uso.

PERMITTED_CHARS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-" 
someString = "".join(c for c in someString if c in PERMITTED_CHARS)
BuvinJ
fonte
2
Em vez de codificar os caracteres permitidos, que são propensos a erros sutis, use string.digits + string.ascii_letters + '_-'.
Reti43
Sua sugestão não está errada, mas também não salva muitos caracteres de "digitação" se esse for seu objetivo. Se você copiar minha postagem, também não terá um erro de digitação! O ponto real, no entanto, da minha resposta é permitir um meio explícito, aberto e simples para definir exatamente quais caracteres você deseja permitir.
BuvinJ
Como um meio termo, você pode combinar essas sugestões em SPECIAL_CHARS = '_-'e, em seguida, usarstring.digits + string.ascii_letters + SPECIAL_CHARS
BuvinJ
Foi uma sugestão em termos do que é razoável, a menos que estejamos praticando golfe de código. "Passear" pelo teclado para digitar 52 letras do alfabeto em ordem leva muito mais tempo do que importar um pacote para usar um ou dois objetos. E isso não inclui o tempo para verificar se você digitou tudo corretamente. É sobre boas práticas, só isso.
Reti43
Eu te escuto! Meu ponto real aqui é extrema flexibilidade, caso você queira ser mais específico com seu conjunto de caracteres.
BuvinJ
4
sent = "".join(e for e in sent if e.isalpha())
Tom Kalvijn
fonte
Vou tentar explicar: ele passa por todos os caracteres da string e for e in sente verifica via if e.isalpha()declaração se o caractere atual é um símbolo alfabético; se assim for - junta-o à sentvariável via sent = "".join()e todos os símbolos não alfabéticos serão substituídos por ""(string vazia) porque de joinfunção.
Sysanin 14/09/19
como isso está fazendo um loop por caractere em vez de depender do regex C, isso não é extremamente lento?
dcsan 31/12/19
3
for char in my_string:
    if not char.isalnum():
        my_string = my_string.replace(char,"")
Junior Ogun
fonte
2

Tempo com seqüências aleatórias de caracteres de impressão ASCII:

from inspect import getsource
from random import sample
import re
from string import printable
from timeit import timeit

pattern_single = re.compile(r'[\W]')
pattern_repeat = re.compile(r'[\W]+')
translation_tb = str.maketrans('', '', ''.join(c for c in map(chr, range(256)) if not c.isalnum()))


def generate_test_string(length):
    return ''.join(sample(printable, length))


def main():
    for i in range(0, 60, 10):
        for test in [
            lambda: ''.join(c for c in generate_test_string(i) if c.isalnum()),
            lambda: ''.join(filter(str.isalnum, generate_test_string(i))),
            lambda: re.sub(r'[\W]', '', generate_test_string(i)),
            lambda: re.sub(r'[\W]+', '', generate_test_string(i)),
            lambda: pattern_single.sub('', generate_test_string(i)),
            lambda: pattern_repeat.sub('', generate_test_string(i)),
            lambda: generate_test_string(i).translate(translation_tb),

        ]:
            print(timeit(test), i, getsource(test).lstrip('            lambda: ').rstrip(',\n'), sep='\t')


if __name__ == '__main__':
    main()

Resultado (Python 3.7):

       Time       Length                           Code                           
6.3716264850008880  00  ''.join(c for c in generate_test_string(i) if c.isalnum())
5.7285426190064750  00  ''.join(filter(str.isalnum, generate_test_string(i)))
8.1875841680011940  00  re.sub(r'[\W]', '', generate_test_string(i))
8.0002205439959650  00  re.sub(r'[\W]+', '', generate_test_string(i))
5.5290945199958510  00  pattern_single.sub('', generate_test_string(i))
5.4417179649972240  00  pattern_repeat.sub('', generate_test_string(i))
4.6772285089973590  00  generate_test_string(i).translate(translation_tb)
23.574712151996210  10  ''.join(c for c in generate_test_string(i) if c.isalnum())
22.829975890002970  10  ''.join(filter(str.isalnum, generate_test_string(i)))
27.210196289997840  10  re.sub(r'[\W]', '', generate_test_string(i))
27.203713296003116  10  re.sub(r'[\W]+', '', generate_test_string(i))
24.008979928999906  10  pattern_single.sub('', generate_test_string(i))
23.945240008994006  10  pattern_repeat.sub('', generate_test_string(i))
21.830899796994345  10  generate_test_string(i).translate(translation_tb)
38.731336012999236  20  ''.join(c for c in generate_test_string(i) if c.isalnum())
37.942474347000825  20  ''.join(filter(str.isalnum, generate_test_string(i)))
42.169366310001350  20  re.sub(r'[\W]', '', generate_test_string(i))
41.933375883003464  20  re.sub(r'[\W]+', '', generate_test_string(i))
38.899814646996674  20  pattern_single.sub('', generate_test_string(i))
38.636144253003295  20  pattern_repeat.sub('', generate_test_string(i))
36.201238164998360  20  generate_test_string(i).translate(translation_tb)
49.377356811004574  30  ''.join(c for c in generate_test_string(i) if c.isalnum())
48.408927293996385  30  ''.join(filter(str.isalnum, generate_test_string(i)))
53.901889764994850  30  re.sub(r'[\W]', '', generate_test_string(i))
52.130339455994545  30  re.sub(r'[\W]+', '', generate_test_string(i))
50.061149017004940  30  pattern_single.sub('', generate_test_string(i))
49.366573111998150  30  pattern_repeat.sub('', generate_test_string(i))
46.649754120997386  30  generate_test_string(i).translate(translation_tb)
63.107938601999194  40  ''.join(c for c in generate_test_string(i) if c.isalnum())
65.116287978999030  40  ''.join(filter(str.isalnum, generate_test_string(i)))
71.477421126997800  40  re.sub(r'[\W]', '', generate_test_string(i))
66.027950693998720  40  re.sub(r'[\W]+', '', generate_test_string(i))
63.315361931003280  40  pattern_single.sub('', generate_test_string(i))
62.342320287003530  40  pattern_repeat.sub('', generate_test_string(i))
58.249303059004890  40  generate_test_string(i).translate(translation_tb)
73.810345625002810  50  ''.join(c for c in generate_test_string(i) if c.isalnum())
72.593953348005020  50  ''.join(filter(str.isalnum, generate_test_string(i)))
76.048324580995540  50  re.sub(r'[\W]', '', generate_test_string(i))
75.106637657001560  50  re.sub(r'[\W]+', '', generate_test_string(i))
74.681338128997600  50  pattern_single.sub('', generate_test_string(i))
72.430461594005460  50  pattern_repeat.sub('', generate_test_string(i))
69.394243567003290  50  generate_test_string(i).translate(translation_tb)

str.maketrans& str.translateé mais rápido, mas inclui todos os caracteres não ASCII. re.compile& pattern.subé mais lento, mas é mais rápido que ''.join& filter.

Solomon Ucko
fonte
-1

Se entendi corretamente, a maneira mais fácil é usar a expressão regular, pois fornece muita flexibilidade, mas o outro método simples é usar o seguinte para o loop: o código com o exemplo. Também contei a ocorrência de palavras e armazenei no dicionário.

s = """An... essay is, generally, a piece of writing that gives the author's own 
argument — but the definition is vague, 
overlapping with those of a paper, an article, a pamphlet, and a short story. Essays 
have traditionally been 
sub-classified as formal and informal. Formal essays are characterized by "serious 
purpose, dignity, logical 
organization, length," whereas the informal essay is characterized by "the personal 
element (self-revelation, 
individual tastes and experiences, confidential manner), humor, graceful style, 
rambling structure, unconventionality 
or novelty of theme," etc.[1]"""

d = {}      # creating empty dic      
words = s.split() # spliting string and stroing in list
for word in words:
    new_word = ''
    for c in word:
        if c.isalnum(): # checking if indiviual chr is alphanumeric or not
            new_word = new_word + c
    print(new_word, end=' ')
    # if new_word not in d:
    #     d[new_word] = 1
    # else:
    #     d[new_word] = d[new_word] +1
print(d)

classifique-o se esta resposta for útil!

Abhishek Pratap Singh
fonte