Python: método eficaz para eliminar todas las letras que no son letras y reemplazarlos con guiones bajos

def format_title(title): ''.join(map(lambda x: x if (x.isupper() or x.islower()) else '_', title.strip())) 

¿Algo más rápido?

La forma más rápida de hacerlo es usar str.translate() Esto es ~ 50 veces más rápido que tu manera

 # You only need to do this once >>> title_trans=''.join(chr(c) if chr(c).isupper() or chr(c).islower() else '_' for c in range(256)) >>> "abcde1234!@%^".translate(title_trans) 'abcde________' # Using map+lambda $ python -m timeit '"".join(map(lambda x: x if (x.isupper() or x.islower()) else "_", "abcd1234!@#$".strip()))' 10000 loops, best of 3: 21.9 usec per loop # Using str.translate $ python -m timeit -s 'titletrans="".join(chr(c) if chr(c).isupper() or chr(c).islower() else "_" for c in range(256))' '"abcd1234!@#$".translate(titletrans)' 1000000 loops, best of 3: 0.422 usec per loop # Here is regex for a comparison $ python -m timeit -s 'import re;transre=re.compile("[\W\d]+")' 'transre.sub("_","abcd1234!@#$")' 100000 loops, best of 3: 3.17 usec per loop 

Aquí hay una versión para unicode.

 # coding: UTF-8 def format_title_unicode_translate(title): return title.translate(title_unicode_trans) class TitleUnicodeTranslate(dict): def __missing__(self,item): uni = unichr(item) res = u"_" if uni.isupper() or uni.islower(): res = uni self[item] = res return res title_unicode_trans=TitleUnicodeTranslate() print format_title_unicode_translate(u"Metallica Μεταλλικα") 

Tenga en cuenta que las letras griegas cuentan como superior e inferior, por lo que no están sustituidas. Si van a ser sustituidos, simplemente cambie la condición a

  if item<256 and (uni.isupper() or uni.islower()): 
 import re title = re.sub("[\W\d]", "_", title.strip()) 

debería ser más rápido

Si desea reemplazar una sucesión de letras no adyacentes por un solo guión bajo, use

 title = re.sub("[\W\d]+", "_", title.strip()) 

en cambio, que es aún más rápido.

Acabo de hacer una comparación de tiempo:

 C:\>python -m timeit -n 100 -s "data=open('test.txt').read().strip()" "''.join(map(lambda x: x if (x.isupper() or x.islower()) else '_', data))" 100 loops, best of 3: 4.51 msec per loop C:\>python -m timeit -n 100 -s "import re; regex=re.compile('[\W\d]+'); data=open('test.txt').read().strip()" "title=regex.sub('_',data)" 100 loops, best of 3: 2.35 msec per loop 

Esto también funcionará en cadenas Unicode (bajo Python 3, \W coincide con cualquier carácter que no sea un carácter de palabra Unicode. Bajo Python 2, tendría que configurar adicionalmente la bandera de UNICODE para esto).

En lugar de (x.isupper() or x.islower()) debería poder usar x.isalpha() . El método isalpha() puede devolver True para '_' (no recuerdo si lo hace o no), pero luego terminará reemplazando '_' con '_' por lo que no habrá daño. (Gracias por señalarlo, KennyTM.)

Curioso sobre esto por mis propios motivos, escribí un guión rápido para probar los diferentes enfoques que se enumeran aquí junto con solo eliminar el lambda que esperaba (incorrectamente) aceleraría la solución original.

La versión corta es que el enfoque de str.translate sopla a los demás. Como un lado, la solución de expresiones regulares, mientras que un segundo cercano, está en lo correcto como está escrito anteriormente.

Aquí está mi progtwig de prueba:

 import re from time import time def format_title(title): return ''.join(map(lambda x: x if (x.isupper() or x.islower()) else "_", title.strip())) def format_title_list_comp(title): return ''.join([x if x.isupper() or x.islower() else "_" for x in title.strip()]) def format_title_list_comp_is_alpha(title): return ''.join([x if x.isalpha() else "_" for x in title.strip()]) def format_title_is_alpha(title): return ''.join(map(lambda x: x if x.isalpha() else '_', title.strip())) def format_title_no_lambda(title): def trans(c): if c.isupper() or c.islower(): return c return "_" return ''.join(map(trans, title.strip())) def format_title_no_lambda_is_alpha(title): def trans(c): if c.isalpha(): return c return "_" return ''.join(map(trans, title.strip())) def format_title_re(title): return re.sub("[\W\d]+", "_", title.strip()) def format_title_re_corrected(title): return re.sub("[\W\d]", "_", title.strip()) TITLE_TRANS = ''.join(chr(c) if chr(c).isalpha() else '_' for c in range(256)) def format_title_with_translate(title): return title.translate(TITLE_TRANS) ITERATIONS = 200000 EXAMPLE_TITLE = "abc123def_$%^!FOO BAR*bazx-bif" def timetest(f): start = time() for i in xrange(ITERATIONS): result = f(EXAMPLE_TITLE) diff = time() - start return result, diff baseline_result, baseline_time = timetest(format_title) def print_result(f, result, time): if result == baseline_result: msg = "CORRECT" else: msg = "INCORRECT" diff = time - baseline_time if diff < 0: indicator = "" else: indicator = "+" pct = (diff / baseline_time) * 100 print "%s: %0.3fs %s%0.3fs [%s%0.4f%%] (%s - %s)" % ( f.__name__, time, indicator, diff, indicator, pct, result, msg) print_result(format_title, baseline_result, baseline_time) print "----" for f in [format_title_is_alpha, format_title_list_comp, format_title_list_comp_is_alpha, format_title_no_lambda, format_title_no_lambda_is_alpha, format_title_re, format_title_re_corrected, format_title_with_translate]: alt_result, alt_time = timetest(f) print_result(f, alt_result, alt_time) 

Y aquí están los resultados:

 format_title: 3.121s +0.000s [+0.0000%] (abc___def_____FOO_BAR_bazx_bif - CORRECT) ---- format_title_is_alpha: 2.336s -0.785s [-25.1470%] (abc___def_____FOO_BAR_bazx_bif - CORRECT) format_title_list_comp: 2.369s -0.751s [-24.0773%] (abc___def_____FOO_BAR_bazx_bif - CORRECT) format_title_list_comp_is_alpha: 1.735s -1.386s [-44.4021%] (abc___def_____FOO_BAR_bazx_bif - CORRECT) format_title_no_lambda: 2.992s -0.129s [-4.1336%] (abc___def_____FOO_BAR_bazx_bif - CORRECT) format_title_no_lambda_is_alpha: 2.377s -0.744s [-23.8314%] (abc___def_____FOO_BAR_bazx_bif - CORRECT) format_title_re: 1.290s -1.831s [-58.6628%] (abc_def__FOO_BAR_bazx_bif - INCORRECT) format_title_re_corrected: 1.338s -1.782s [-57.1165%] (abc___def_____FOO_BAR_bazx_bif - CORRECT) format_title_with_translate: 0.098s -3.022s [-96.8447%] (abc___def_____FOO_BAR_bazx_bif - CORRECT) 
  • EDITADO: Agregué una variación que muestra que las comprensiones de la lista mejoran significativamente la implementación original, así como una implementación de expresiones regulares correcta que muestra que aún es casi tan rápida cuando es correcta. Por supuesto, str.translate todavía gana sin parar.
 import string,sys letters=string.letters mystring = list("abc134#$@e##$%%$*&(()#def") for n,c in enumerate(mystring): if not c in letters: mystring[n]="_" print ''.join(mystring)