Sé cómo hacer esto si repito todos los caracteres de la cadena, pero estoy buscando un método más elegante.
Sé cómo hacer esto si repito todos los caracteres de la cadena, pero estoy buscando un método más elegante.
Respuestas:
Una expresión regular funcionará con muy poco código:
import re
...
if re.match("^[A-Za-z0-9_-]*$", my_little_string):
# do something here
\w
incluye \d
y _
, por lo tanto isvalid = re.match(r'[\w-]+$', astr)
o isinvalid = re.search(r'[^\w-]', astr)
. La posible presencia de locale.setlocale
cadenas Unicode o de cadenas requiere una consideración adicional.
isvalid = re.match(r'[\w-]*$', astr)
- las cadenas vacías son válidas.
[Editar] Hay otra solución que aún no se menciona, y parece superar a las demás dadas hasta ahora en la mayoría de los casos.
Use string.translate para reemplazar todos los caracteres válidos en la cadena, y vea si nos quedan algunos inválidos. Esto es bastante rápido, ya que usa la función C subyacente para hacer el trabajo, con muy poco código de bytes de Python involucrado.
Obviamente, el rendimiento no lo es todo: buscar las soluciones más legibles es probablemente el mejor enfoque cuando no se encuentra en una ruta de código crítica para el rendimiento, pero solo para ver cómo se comparan las soluciones, aquí hay una comparación de rendimiento de todos los métodos propuestos hasta ahora. check_trans es el que usa el método string.translate.
Código de prueba:
import string, re, timeit
pat = re.compile('[\w-]*$')
pat_inv = re.compile ('[^\w-]')
allowed_chars=string.ascii_letters + string.digits + '_-'
allowed_set = set(allowed_chars)
trans_table = string.maketrans('','')
def check_set_diff(s):
return not set(s) - allowed_set
def check_set_all(s):
return all(x in allowed_set for x in s)
def check_set_subset(s):
return set(s).issubset(allowed_set)
def check_re_match(s):
return pat.match(s)
def check_re_inverse(s): # Search for non-matching character.
return not pat_inv.search(s)
def check_trans(s):
return not s.translate(trans_table,allowed_chars)
test_long_almost_valid='a_very_long_string_that_is_mostly_valid_except_for_last_char'*99 + '!'
test_long_valid='a_very_long_string_that_is_completely_valid_' * 99
test_short_valid='short_valid_string'
test_short_invalid='/$%$%&'
test_long_invalid='/$%$%&' * 99
test_empty=''
def main():
funcs = sorted(f for f in globals() if f.startswith('check_'))
tests = sorted(f for f in globals() if f.startswith('test_'))
for test in tests:
print "Test %-15s (length = %d):" % (test, len(globals()[test]))
for func in funcs:
print " %-20s : %.3f" % (func,
timeit.Timer('%s(%s)' % (func, test), 'from __main__ import pat,allowed_set,%s' % ','.join(funcs+tests)).timeit(10000))
print
if __name__=='__main__': main()
Los resultados en mi sistema son:
Test test_empty (length = 0):
check_re_inverse : 0.042
check_re_match : 0.030
check_set_all : 0.027
check_set_diff : 0.029
check_set_subset : 0.029
check_trans : 0.014
Test test_long_almost_valid (length = 5941):
check_re_inverse : 2.690
check_re_match : 3.037
check_set_all : 18.860
check_set_diff : 2.905
check_set_subset : 2.903
check_trans : 0.182
Test test_long_invalid (length = 594):
check_re_inverse : 0.017
check_re_match : 0.015
check_set_all : 0.044
check_set_diff : 0.311
check_set_subset : 0.308
check_trans : 0.034
Test test_long_valid (length = 4356):
check_re_inverse : 1.890
check_re_match : 1.010
check_set_all : 14.411
check_set_diff : 2.101
check_set_subset : 2.333
check_trans : 0.140
Test test_short_invalid (length = 6):
check_re_inverse : 0.017
check_re_match : 0.019
check_set_all : 0.044
check_set_diff : 0.032
check_set_subset : 0.037
check_trans : 0.015
Test test_short_valid (length = 18):
check_re_inverse : 0.125
check_re_match : 0.066
check_set_all : 0.104
check_set_diff : 0.051
check_set_subset : 0.046
check_trans : 0.017
El enfoque de traducción parece mejor en la mayoría de los casos, dramáticamente con cadenas largas válidas, pero es superado por expresiones regulares en test_long_invalid (presumiblemente porque la expresión regular puede rescatar inmediatamente, pero la traducción siempre tiene que escanear toda la cadena). Los enfoques establecidos suelen ser los peores, superando las expresiones regulares solo para el caso de cadenas vacías.
El uso de all (x en permitido_set para x en s) funciona bien si se rescata temprano, pero puede ser malo si tiene que iterar a través de cada carácter. isSubSet y set difference son comparables y son consistentemente proporcionales a la longitud de la cadena independientemente de los datos.
Existe una diferencia similar entre los métodos de expresiones regulares que coinciden con todos los caracteres válidos y la búsqueda de caracteres no válidos. La coincidencia funciona un poco mejor cuando se busca una cadena larga pero completamente válida, pero peor para los caracteres no válidos cerca del final de la cadena.
string.ascii_letters
lugar de string.letters
si no usa el indicador re.LOCALE para las expresiones regulares (de lo contrario, podría obtener resultados falsos positivos check_trans()
. string.maketrans()
No funcionará para cadenas Unicode.)
from __future__ import unicode_literals
), use trans_table3 = dict((ord(char), '') for char in allowed_chars)
y def check_trans(s): return not s.translate(trans_table3)
. Pero, en general, funciona peor que las versiones RE.
Hay una variedad de formas de lograr este objetivo, algunas son más claras que otras. Para cada uno de mis ejemplos, 'Verdadero' significa que la cadena pasada es válida, 'Falso' significa que contiene caracteres no válidos.
En primer lugar, está el enfoque ingenuo:
import string
allowed = string.letters + string.digits + '_' + '-'
def check_naive(mystring):
return all(c in allowed for c in mystring)
Luego está el uso de una expresión regular, puede hacer esto con re.match (). Tenga en cuenta que '-' debe estar al final de []; de lo contrario, se utilizará como delimitador de 'rango'. También tenga en cuenta el $ que significa 'final de cadena'. Otras respuestas indicadas en esta pregunta usan una clase de caracteres especial, '\ w', siempre prefiero usar un rango de clase de caracteres explícito usando [] porque es más fácil de entender sin tener que buscar una guía de referencia rápida, y más fácil de especial- caso.
import re
CHECK_RE = re.compile('[a-zA-Z0-9_-]+$')
def check_re(mystring):
return CHECK_RE.match(mystring)
Otra solución señaló que puede hacer una coincidencia inversa con expresiones regulares, lo he incluido aquí ahora. Tenga en cuenta que [^ ...] invierte la clase de caracteres porque se usa ^:
CHECK_INV_RE = re.compile('[^a-zA-Z0-9_-]')
def check_inv_re(mystring):
return not CHECK_INV_RE.search(mystring)
También puedes hacer algo complicado con el objeto 'set'. Eche un vistazo a este ejemplo, que elimina de la cadena original todos los caracteres que están permitidos, dejándonos con un conjunto que contiene a) nada, ob) los caracteres ofensivos de la cadena:
def check_set(mystring):
return not set(mystring) - set(allowed)
string.ascii_letters
si usa expresiones regulares '[a-zA-Z]'.
Si no fuera por los guiones y guiones bajos, la solución más sencilla sería
my_little_string.isalnum()
(Sección 3.6.1 de la Referencia de la biblioteca de Python)
La expresión regular puede ser muy flexible.
import re;
re.fullmatch("^[\w-]+$", target_string) # fullmatch looks also workable for python 3.4
\w
: Solamente [a-zA-Z0-9_]
Por lo tanto, debe agregar -
char para justificar el guión char.
+
: Coincide con una o más repeticiones del carácter anterior. Supongo que no acepta entradas en blanco. Pero si lo hace, cambie a *
.
^
: Coincide con el inicio de la cadena.
$
: Coincide con el final de la cadena.
Necesita estos dos caracteres especiales ya que debe evitar el siguiente caso. Los caracteres no deseados como &
aquí pueden aparecer entre el patrón coincidente.
&&&PATTERN&&PATTERN
Bueno, puedes pedir la ayuda de regex, el genial aquí :)
código:
import re
string = 'adsfg34wrtwe4r2_()' #your string that needs to be matched.
regex = r'^[\w\d_()]*$' # you can also add a space in regex if u want to allow it in the string
if re.match(regex,string):
print 'yes'
else:
print 'false'
Salida:
yes
Espero que esto ayude :)
Siempre puede usar una lista de comprensión y verificar los resultados con todos, sería un poco menos intensivo en recursos que usar una expresión regular: all([c in string.letters + string.digits + ["_", "-"] for c in mystring])
Aquí hay algo basado en el "enfoque ingenuo" de Jerub (¡ingenuo son sus palabras, no las mías!):
import string
ALLOWED = frozenset(string.ascii_letters + string.digits + '_' + '-')
def check(mystring):
return all(c in ALLOWED for c in mystring)
Si ALLOWED
fuera una cadena, creo c in ALLOWED
que implicaría iterar sobre cada carácter de la cadena hasta que encuentre una coincidencia o llegue al final. Lo cual, para citar a Joel Spolsky, es una especie de algoritmo de Shlemiel the Painter .
Pero probar la existencia en un conjunto debería ser más eficiente, o al menos menos dependiente del número de caracteres permitidos. Ciertamente, este enfoque es un poco más rápido en mi máquina. Está claro y creo que funciona bastante bien en la mayoría de los casos (en mi máquina lenta puedo validar decenas de miles de cadenas cortas en una fracción de segundo). Me gusta.
REALMENTE en mi máquina, una expresión regular funciona varias veces más rápido, y es tan simple como esto (posiblemente más simple). Probablemente esa sea la mejor manera de avanzar.
¡use una expresión regular y vea si coincide!
([a-z][A-Z][0-9]\_\-)*