python regex search character

title in python plot



En Python, ¿cómo verificar si una cadena solo contiene ciertos caracteres? (5)

En Python, ¿cómo verificar si una cadena solo contiene ciertos caracteres?

Necesito verificar una cadena que contenga solo a ...z, 0..9 y. (punto) y ningún otro personaje.

Podría repetir sobre cada personaje y verificar que el carácter sea a ... z o 0..9, o. pero eso sería lento.

No tengo claro ahora cómo hacerlo con una expresión regular.

¿Es esto correcto? ¿Puede sugerir una expresión regular más simple o un enfoque más eficiente?

#Valid chars . a-z 0-9 def check(test_str): import re #http://docs.python.org/library/re.html #re.search returns None if no position in the string matches the pattern #pattern to search for any character other then . a-z 0-9 pattern = r''[^/.a-z0-9]'' if re.search(pattern, test_str): #Character other then . a-z 0-9 was found print ''Invalid : %r'' % (test_str,) else: #No character other then . a-z 0-9 was found print ''Valid : %r'' % (test_str,) check(test_str=''abcde.1'') check(test_str=''abcde.1#'') check(test_str=''ABCDE.12'') check(test_str=''_-/>"!@#12345abcde<'') '''''' Output: >>> Valid : "abcde.1" Invalid : "abcde.1#" Invalid : "ABCDE.12" Invalid : "_-/>"!@#12345abcde<" ''''''


Aquí hay una implementación simple y pura de Python. Se debe usar cuando el rendimiento no es crítico (incluido para futuros Googlers).

import string allowed = set(string.ascii_lowercase + string.digits + ''.'') def check(test_str): set(test_str) <= allowed

En cuanto al rendimiento, la iteración probablemente será el método más rápido. Las expresiones regulares tienen que iterar a través de una máquina de estados, y la solución de igualdad establecida debe construir un conjunto temporal. Sin embargo, es poco probable que la diferencia importe mucho. Si el rendimiento de esta función es muy importante, escríbalo como un módulo de extensión C con una instrucción switch (que se compilará en una tabla de salto).

Aquí hay una implementación de C, que usa sentencias if debido a restricciones de espacio. Si necesitas absolutamente algo de velocidad extra, escribe la caja del interruptor. En mis pruebas, funciona muy bien (2 segundos frente a 9 segundos en puntos de referencia contra la expresión regular).

#define PY_SSIZE_T_CLEAN #include <Python.h> static PyObject *check(PyObject *self, PyObject *args) { const char *s; Py_ssize_t count, ii; char c; if (0 == PyArg_ParseTuple (args, "s#", &s, &count)) { return NULL; } for (ii = 0; ii < count; ii++) { c = s[ii]; if ((c < ''0'' && c != ''.'') || c > ''z'') { Py_RETURN_FALSE; } if (c > ''9'' && c < ''a'') { Py_RETURN_FALSE; } } Py_RETURN_TRUE; } PyDoc_STRVAR (DOC, "Fast stringcheck"); static PyMethodDef PROCEDURES[] = { {"check", (PyCFunction) (check), METH_VARARGS, NULL}, {NULL, NULL} }; PyMODINIT_FUNC initstringcheck (void) { Py_InitModule3 ("stringcheck", PROCEDURES, DOC); }

Incluirlo en su setup.py:

from distutils.core import setup, Extension ext_modules = [ Extension (''stringcheck'', [''stringcheck.c'']), ],

Usar como:

>>> from stringcheck import check >>> check("abc") True >>> check("ABC") False


Enfoque más simple? ¿Un poco más Pythonic?

>>> ok = "0123456789abcdef" >>> all(c in ok for c in "123456abc") True >>> all(c in ok for c in "hello world") False

Ciertamente no es el más eficiente, pero es seguro de leer.


Esto ya ha sido respondido satisfactoriamente, pero para las personas que se enteran de esto después del hecho, he hecho algunos perfiles de varios métodos diferentes para lograr esto. En mi caso, quería dígitos hexadecimales en mayúsculas, así que modifíquelos según sea necesario para satisfacer sus necesidades.

Aquí están mis implementaciones de prueba:

import re hex_digits = set("ABCDEF1234567890") hex_match = re.compile(r''^[A-F0-9]+/Z'') hex_search = re.compile(r''[^A-F0-9]'') def test_set(input): return set(input) <= hex_digits def test_not_any(input): return not any(c not in hex_digits for c in input) def test_re_match1(input): return bool(re.compile(r''^[A-F0-9]+/Z'').match(input)) def test_re_match2(input): return bool(hex_match.match(input)) def test_re_match3(input): return bool(re.match(r''^[A-F0-9]+/Z'', input)) def test_re_search1(input): return not bool(re.compile(r''[^A-F0-9]'').search(input)) def test_re_search2(input): return not bool(hex_search.search(input)) def test_re_search3(input): return not bool(re.match(r''[^A-F0-9]'', input))

Y las pruebas, en Python 3.4.0 en Mac OS X:

import cProfile import pstats import random # generate a list of 10000 random hex strings between 10 and 10009 characters long # this takes a little time; be patient tests = [ ''''.join(random.choice("ABCDEF1234567890") for _ in range(l)) for l in range(10, 10010) ] # set up profiling, then start collecting stats test_pr = cProfile.Profile(timeunit=0.000001) test_pr.enable() # run the test functions against each item in tests. # this takes a little time; be patient for t in tests: for tf in [test_set, test_not_any, test_re_match1, test_re_match2, test_re_match3, test_re_search1, test_re_search2, test_re_search3]: _ = tf(t) # stop collecting stats test_pr.disable() # we create our own pstats.Stats object to filter # out some stuff we don''t care about seeing test_stats = pstats.Stats(test_pr) # normally, stats are printed with the format %8.3f, # but I want more significant digits # so this monkey patch handles that def _f8(x): return "%11.6f" % x def _print_title(self): print('' ncalls tottime percall cumtime percall'', end='' '', file=self.stream) print(''filename:lineno(function)'', file=self.stream) pstats.f8 = _f8 pstats.Stats.print_title = _print_title # sort by cumulative time (then secondary sort by name), ascending # then print only our test implementation function calls: test_stats.sort_stats(''cumtime'', ''name'').reverse_order().print_stats("test_*")

que dio los siguientes resultados:

50335004 function calls in 13.428 seconds Ordered by: cumulative time, function name List reduced from 20 to 8 due to restriction ncalls tottime percall cumtime percall filename:lineno(function) 10000 0.005233 0.000001 0.367360 0.000037 :1(test_re_match2) 10000 0.006248 0.000001 0.378853 0.000038 :1(test_re_match3) 10000 0.010710 0.000001 0.395770 0.000040 :1(test_re_match1) 10000 0.004578 0.000000 0.467386 0.000047 :1(test_re_search2) 10000 0.005994 0.000001 0.475329 0.000048 :1(test_re_search3) 10000 0.008100 0.000001 0.482209 0.000048 :1(test_re_search1) 10000 0.863139 0.000086 0.863139 0.000086 :1(test_set) 10000 0.007414 0.000001 9.962580 0.000996 :1(test_not_any)

dónde:

ncalls
La cantidad de veces que se llamó esa función
tottime
el tiempo total empleado en la función dada, excluyendo el tiempo asignado a las funciones secundarias
por llamada
el cociente de tottime dividido por ncalls
cumtime
el tiempo acumulado en esta y todas las subfunciones
por llamada
el cociente del tiempo acumulado dividido por llamadas primitivas

Las columnas que realmente nos importan son el tiempo de espera y el período de recuperación, ya que nos muestra el tiempo real que tarda la entrada de la función en salir. Como podemos ver, la coincidencia de expresiones geográficas y la búsqueda no son masivamente diferentes.

Es más rápido no molestarse en compilar la expresión regular si la hubiera compilado todo el tiempo. Es aproximadamente un 7,5% más rápido compilar una vez que cada vez, pero solo un 2,5% más rápido para compilar que para no compilar.

test_set fue dos veces más lento que re_search y tres veces más lento que re_match

test_not_any fue un orden completo de magnitud más lento que test_set

TL; DR : use re.match o re.search


EDITAR: Cambió la expresión regular para excluir AZ

La solución de expresión regular es la solución de python pura más rápida hasta ahora

reg=re.compile(''^[a-z0-9/.]+$'') >>>reg.match(''jsdlfjdsf12324..3432jsdflsdf'') True >>> timeit.Timer("reg.match(''jsdlfjdsf12324..3432jsdflsdf'')", "import re; reg=re.compile(''^[a-z0-9/.]+$'')").timeit() 0.70509696006774902

Comparado con otras soluciones:

>>> timeit.Timer("set(''jsdlfjdsf12324..3432jsdflsdf'') <= allowed", "import string; allowed = set(string.ascii_lowercase + string.digits + ''.'')").timeit() 3.2119350433349609 >>> timeit.Timer("all(c in allowed for c in ''jsdlfjdsf12324..3432jsdflsdf'')", "import string; allowed = set(string.ascii_lowercase + string.digits + ''.'')").timeit() 6.7066690921783447

Si desea permitir cadenas vacías, cámbielo a:

reg=re.compile(''^[a-z0-9/.]*$'') >>>reg.match('''') False

Bajo petición voy a devolver la otra parte de la respuesta. Pero tenga en cuenta que los siguientes aceptan el rango AZ.

Puedes usar isalnum

test_str.replace(''.'', '''').isalnum() >>> ''test123.3''.replace(''.'', '''').isalnum() True >>> ''test123-3''.replace(''.'', '''').isalnum() False

EDITAR Usar isalnum es mucho más eficiente que la solución establecida

>>> timeit.Timer("''jsdlfjdsf12324..3432jsdflsdf''.replace(''.'', '''').isalnum()").timeit() 0.63245487213134766

EDIT2 John dio un ejemplo en el que lo anterior no funciona. Cambié la solución para superar este caso especial mediante el uso de codificación

test_str.replace(''.'', '''').encode(''ascii'', ''replace'').isalnum()

Y todavía es casi 3 veces más rápido que la solución establecida

timeit.Timer("u''ABC/u0131/u0661''.encode(''ascii'', ''replace'').replace(''.'','''').isalnum()", "import string; allowed = set(string.ascii_lowercase + string.digits + ''.'')").timeit() 1.5719811916351318

En mi opinión, usar expresiones regulares es lo mejor para resolver este problema


Final (?) Editar

Respuesta, envuelta en una función, con sesión interactiva anotada:

>>> import re >>> def special_match(strg, search=re.compile(r''[^a-z0-9.]'').search): ... return not bool(search(strg)) ... >>> special_match("") True >>> special_match("az09.") True >>> special_match("az09./n") False # The above test case is to catch out any attempt to use re.match() # with a `$` instead of `/Z` -- see point (6) below. >>> special_match("az09.#") False >>> special_match("az09.X") False >>>

Nota: Hay una comparación con el uso de re.match () más abajo en esta respuesta. Los tiempos adicionales muestran que match () ganaría con cadenas mucho más largas; match () parece tener una sobrecarga mucho mayor que search () cuando la respuesta final es True; esto es desconcertante (tal vez es el costo de devolver un MatchObject en lugar de None) y puede garantizar un mayor análisis.

==== Earlier text ====

La respuesta aceptada [previamente] podría usar algunas mejoras:

(1) La presentación da la apariencia de ser el resultado de una sesión interactiva de Python:

reg=re.compile(''^[a-z0-9/.]+$'') >>>reg.match(''jsdlfjdsf12324..3432jsdflsdf'') True

pero match () no devuelve True

(2) Para usar con match (), el ^ al comienzo del patrón es redundante, y parece ser un poco más lento que el mismo patrón sin el ^

(3) Debería fomentar el uso de cadenas sin procesar de forma automática sin pensar en ningún patrón de re

(4) La barra invertida delante del punto / punto es redundante

(5) ¡Más lento que el código de OP!

prompt>rem OP''s version -- NOTE: OP used raw string! prompt>/python26/python -mtimeit -s"t=''jsdlfjdsf12324..3432jsdflsdf'';import re;reg=re.compile(r''[^a-z0-9/.]'')" "not bool(reg.search(t))" 1000000 loops, best of 3: 1.43 usec per loop prompt>rem OP''s version w/o backslash prompt>/python26/python -mtimeit -s"t=''jsdlfjdsf12324..3432jsdflsdf'';import re;reg=re.compile(r''[^a-z0-9.]'')" "not bool(reg.search(t))" 1000000 loops, best of 3: 1.44 usec per loop prompt>rem cleaned-up version of accepted answer prompt>/python26/python -mtimeit -s"t=''jsdlfjdsf12324..3432jsdflsdf'';import re;reg=re.compile(r''[a-z0-9.]+/Z'')" "bool(reg.match(t))" 100000 loops, best of 3: 2.07 usec per loop prompt>rem accepted answer prompt>/python26/python -mtimeit -s"t=''jsdlfjdsf12324..3432jsdflsdf'';import re;reg=re.compile(''^[a-z0-9/.]+$'')" "bool(reg.match(t))" 100000 loops, best of 3: 2.08 usec per loop

(6) ¡ Puede producir la respuesta incorrecta!

>>> import re >>> bool(re.compile(''^[a-z0-9/.]+$'').match(''1234/n'')) True # uh-oh >>> bool(re.compile(''^[a-z0-9/.]+/Z'').match(''1234/n'')) False