Eliminar todo menos caracteres alfanuméricos de una cadena en Python

339

¿Cuál es la mejor manera de quitar todos los caracteres no alfanuméricos de una cadena, usando Python?

Las soluciones presentadas en la variante PHP de esta pregunta probablemente funcionarán con algunos ajustes menores, pero no me parecen muy 'pitónicas'.

Para el registro, no solo quiero quitar puntos y comas (y otros signos de puntuación), sino también citas, corchetes, etc.

Mark van Lent
fuente
8
¿Le interesan los caracteres alfanuméricos internacionales, como 'æøå', 'مرحبا', 'สวัสดี', 'こ ん に ち は'?
Pimin Konstantin Kefaloukos
44
@PiminKonstantinKefaloukos Sí, me importan los caracteres internacionales, de ahí mi comentario sobre la respuesta aceptada para usar re.UNICODE.
Mark van Lent

Respuestas:

337

Acabo de cronometrar algunas funciones por curiosidad. En estas pruebas, estoy eliminando caracteres no alfanuméricos de la cadena string.printable(parte del stringmódulo incorporado ). El uso de compilado '[\W_]+'y pattern.sub('', str)resultó ser el más 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
fuente
2
Resultados muy interesantes: hubiera esperado que las expresiones regulares fueran más lentas. Curiosamente, probé esto con otra opción ( valid_characters = string.ascii_letters + string.digitsseguido por join(ch for ch in string.printable if ch in valid_characters)y fue 6 microsegundos más rápido que la isalnum()opción. Sin embargo, aún mucho más lento que la
expresión regular
+1, ¡medir el tiempo es bueno! (pero en el penúltimo, en su pattern.sub('', string.printable)lugar, ¡tonto llamar a re.sub cuando tienes un objeto RE! -).
Alex Martelli
46
Para el registro: use re.compile('[\W_]+', re.UNICODE)para hacerlo unicode seguro.
Mark van Lent
3
¿Cómo lo haces sin eliminar el espacio en blanco?
maudulus
66
hágalo sin quitar el espacio en blanco: re.sub ('[\ W _] +', '', oración, flags = re.UNICODE)
PALEN
269

Expresiones regulares al rescate:

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

Por definición de Python '\W== [^a-zA-Z0-9_], que excluye todo numbers, lettersy_

Hormigas Aasma
fuente
2
¿Qué hace el signo más en la expresión regular? (Sé lo que significa, solo curiosidad por saber por qué se necesita para el re.sub.)
Mark van Lent
77
@ Mark: Me imagino que aceleraría la sustitución ya que el reemplazo eliminará todos los caracteres que no son palabras en un bloque de una sola vez, en lugar de eliminarlos uno por uno.
DrAl
2
Sí, lo hice mientras ajustaba un código crítico de rendimiento hace un tiempo. Si hay espacios de caracteres significativos para reemplazar, la aceleración es enorme.
Ants Aasma
20
Puede que no sea relevante en este caso, pero \Wtambién mantendrá guiones bajos.
Blixt
12
Siguiendo el consejo de @Blixt, si solo desea letras y números, puede hacer re.sub (r '[^ a-zA-Z0-9]', '', your_string)
Nigini
69

Use el método str.translate () .

Presumiendo que harás esto a menudo:

(1) Una vez, cree una cadena que contenga todos los caracteres que desea eliminar:

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

(2) Cada vez que quieras apretar una cuerda:

scrunched = s.translate(None, delchars)

El costo de instalación probablemente se compara favorablemente con re.compile; el costo marginal es mucho más bajo:

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: El uso de string.printable como datos de referencia proporciona al patrón '[\ W _] +' una ventaja injusta ; todos los caracteres no alfanuméricos están en un grupo ... en los datos típicos habría más de una sustitución que hacer:

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

Esto es lo que sucede si le das un poco más de trabajo a re.sub:

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
fuente
1
El uso de traducir es bastante más rápido. Incluso cuando agrega un bucle for justo antes de hacer la sustitución / traducción (para que los costos de configuración pesen menos), la traducción sigue siendo aproximadamente 17 veces más rápida que la expresión regular en mi máquina. Bueno saber.
Mark van Lent
3
Esta es definitivamente la solución más pitónica.
codygman
1
Esto casi me convence, pero sugeriría usar string.punctuationEn lugar de''.join(c for c in map(chr, range(256)) if not c.isalnum())
ArnauOrriols
1
Tenga en cuenta que esto funciona para strobjetos pero no para unicodeobjetos.
Yavar
@John Machin ¿Es esencialmente una comprensión de la lista que se está pasando como argumento .join()?
AdjunctProfessorFalcon
42

Tu podrías intentar:

print ''.join(ch for ch in some_string if ch.isalnum())
ars
fuente
16
>>> import re
>>> string = "Kl13@£$%[};'\""
>>> pattern = re.compile('\W')
>>> string = re.sub(pattern, '', string)
>>> print string
Kl13
DesplazadoAussie
fuente
1
Me encantó tu respuesta, pero también elimina los caracteres en árabe, ¿puedes decirme cómo conservarlos
Charif DZ
14

Qué tal si:

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

Esto funciona mediante el uso de la comprensión de la lista para producir una lista de los caracteres InputStringsi están presentes en las cadenas ascii_lettersy combinadas digits. Luego une la lista en una cadena.

DrAl
fuente
Parece que string.ascii_letters solo contiene letras (duh) y no números. También necesito los números ...
Mark van Lent
Agregar string.digits resolvería el problema que acabo de mencionar. :)
Mark van Lent
Sí, me di cuenta de eso cuando volví a leer tu pregunta. Nota personal: ¡aprende a leer!
DrAl
5

Como resultado de algunas otras respuestas aquí, ofrezco una forma realmente simple y flexible de definir un conjunto de caracteres a los que desea limitar el contenido de una cadena. En este caso, permito alfanuméricos MÁS guiones y guiones bajos. Simplemente agregue o elimine caracteres de mi PERMITTED_CHARSsegún convenga a su caso de uso.

PERMITTED_CHARS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-" 
someString = "".join(c for c in someString if c in PERMITTED_CHARS)
BuvinJ
fuente
3
En lugar de codificar los caracteres permitidos, que son propensos a errores sutiles, use string.digits + string.ascii_letters + '_-'.
Reti43
Su sugerencia no es incorrecta, pero tampoco guarda muchos caracteres de "mecanografía" si ese es su objetivo. Si copias mi publicación, ¡tampoco tendrás un error tipográfico! El punto real, sin embargo, de mi respuesta es permitir un medio explícito, abierto y simple para definir exactamente qué caracteres desea permitir.
BuvinJ
Como término medio, puede combinar estas sugerencias SPECIAL_CHARS = '_-'y luego usarlasstring.digits + string.ascii_letters + SPECIAL_CHARS
BuvinJ el
Fue una sugerencia en términos de lo que es razonable, a menos que estemos haciendo código de golf. "Caminar" alrededor del teclado para escribir 52 letras del alfabeto en orden toma mucho más tiempo que importar un paquete para usar un objeto o dos. Y eso no incluye el tiempo para verificar que está escrito correctamente. Se trata de buenas prácticas, eso es todo.
Reti43
¡Te escucho! Mi punto real aquí es la flexibilidad extrema, en caso de que desee ser más específico con su conjunto de caracteres.
BuvinJ
5
sent = "".join(e for e in sent if e.isalpha())
Tom Kalvijn
fuente
Trataré de explicar: pasa por todos los caracteres de cadena e for e in senty comprueba mediante una if e.isalpha()declaración si el carácter actual es un símbolo alfabético, si es así, lo une a la sentvariable vía sent = "".join()y todos los símbolos no alfabéticos serán reemplazados por ""(cadena vacía) porque de la joinfunción
Sysanin
dado que esto está haciendo un bucle por personaje en lugar de depender de C regex, ¿no es esto extremadamente lento?
dcsan
3
for char in my_string:
    if not char.isalnum():
        my_string = my_string.replace(char,"")
Junior Ogun
fuente
2

Temporización con cadenas aleatorias de imprimibles 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.translatees el más rápido, pero incluye todos los caracteres no ASCII. re.compile& pattern.subes más lento, pero de alguna manera es más rápido que ''.join& filter.

Solomon Ucko
fuente
-1

Si entendí correctamente, la forma más fácil es usar expresiones regulares, ya que le brinda mucha flexibilidad, pero el otro método simple es usarlo para el seguimiento del bucle es el código con el ejemplo. También conté la aparición de palabras y las almacené en el diccionario.

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)

por favor califique esto si esta respuesta es útil!

Abhishek Pratap Singh
fuente