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