it-swarm.it

Come posso verificare che una stringa contenga solo lettere, numeri, caratteri di sottolineatura e trattini?

So come farlo se faccio scorrere tutti i personaggi nella stringa ma sto cercando un metodo più elegante.

76
Ethan Post

Un'espressione regolare farà il trucco con un codice molto piccolo:

import re

...

if re.match("^[A-Za-z0-9_-]*$", my_little_string):
    # do something here
109
Thomas

[Modifica] C'è un'altra soluzione non menzionata ancora e sembra sovraperformare gli altri dati finora nella maggior parte dei casi.

Utilizzare string.translate per sostituire tutti i caratteri validi nella stringa e vedere se ne rimangono di quelli non validi. Questo è abbastanza veloce in quanto utilizza la funzione C sottostante per eseguire il lavoro, con un piccolo bytecode python coinvolto.

Ovviamente le prestazioni non sono tutto - optare per le soluzioni più leggibili è probabilmente l'approccio migliore quando non in un codepath critico delle prestazioni, ma solo per vedere come si accumulano le soluzioni, ecco un confronto delle prestazioni di tutti i metodi proposti finora. check_trans è quello che usa il metodo string.translate.

Codice di prova:

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()

I risultati sul mio sistema sono:

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

L'approccio di traduzione sembra il migliore nella maggior parte dei casi, in modo drammatico con lunghe stringhe valide, ma viene eliminato dalle espressioni regolari in test_long_invalid (Presumibilmente perché la regex può essere salvata immediatamente, ma tradurre deve sempre eseguire la scansione dell'intera stringa). Gli approcci impostati di solito sono peggiori, battendo le regex solo per la stringa vuota.

L'utilizzo di tutti (x in allowed_set per x in s) ha buone prestazioni se si interrompe precocemente, ma può essere negativo se deve scorrere ogni carattere. isSubSet e set difference sono comparabili e sono costantemente proporzionali alla lunghezza della stringa, indipendentemente dai dati.

C'è una differenza simile tra i metodi regex che corrispondono a tutti i caratteri validi e alla ricerca di caratteri non validi. La corrispondenza si comporta un po 'meglio quando si verifica una stringa lunga, ma completamente valida, ma peggiore per i caratteri non validi verso la fine della stringa.

22
Brian

Esistono diversi modi per raggiungere questo obiettivo, alcuni sono più chiari di altri. Per ciascuno dei miei esempi, "Vero" significa che la stringa passata è valida, "Falso" significa che contiene caratteri non validi.

Prima di tutto, c'è l'approccio ingenuo:

import string
allowed = string.letters + string.digits + '_' + '-'

def check_naive(mystring):
    return all(c in allowed for c in mystring)

Poi c'è l'uso di un'espressione regolare, puoi farlo con re.match (). Nota che '-' deve essere alla fine di [] altrimenti sarà usato come delimitatore 'intervallo'. Nota anche $ che significa 'fine della stringa'. Altre risposte annotate in questa domanda usano una classe di caratteri speciale, '\ w', preferisco sempre usare un intervallo di classe di caratteri esplicito usando [] perché è più facile da capire senza dover cercare una guida di riferimento rapida e più facile da specializzare- Astuccio.

import re
CHECK_RE = re.compile('[a-zA-Z0-9_-]+$')
def check_re(mystring):
    return CHECK_RE.match(mystring)

Un'altra soluzione ha rilevato che è possibile eseguire una corrispondenza inversa con espressioni regolari, l'ho inclusa qui ora. Nota che [^ ...] inverte la classe di caratteri perché ^ è usato:

CHECK_INV_RE = re.compile('[^a-zA-Z0-9_-]')
def check_inv_re(mystring):
   return not CHECK_INV_RE.search(mystring)

Puoi anche fare qualcosa di complicato con l'oggetto 'set'. Dai un'occhiata a questo esempio, che rimuove dalla stringa originale tutti i caratteri consentiti, lasciandoci un set contenente a) nothing, o b) i caratteri offendenti dalla stringa:

def check_set(mystring):
    return not set(mystring) - set(allowed)
15
Jerub

Se non fosse per trattini e underscore, la soluzione più semplice sarebbe

my_little_string.isalnum()

(Sezione 3.6.1 del riferimento alla libreria Python)

11
Ber

In alternativa all'uso della regex puoi farlo in Set:

from sets import Set

allowed_chars = Set('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-')

if Set(my_little_sting).issubset(allowed_chars):
    # your action
    print True
5
Ber
 pat = re.compile ('[^\w-]')

 def onlyallowed(s):
    return not pat.search (s)
3
Javier

Bene, puoi chiedere l'aiuto di regex, il bello qui :)

codice:

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'

Produzione:

yes  

Spero che questo ti aiuti :)

1

Puoi sempre usare una comprensione delle liste e controllare i risultati con tutti, sarebbe un po 'meno dispendioso in termini di risorse rispetto all'utilizzo di una regex: all([c in string.letters + string.digits + ["_", "-"] for c in mystring])

0
William Keller

L'espressione regolare può essere molto flessibile. 

import re;
re.fullmatch("^[\w-]+$", target_string) # fullmatch starts from python 3.4 `match` looks also workable here

\w: solo [a-zA-Z0-9_]

Quindi è necessario aggiungere - char.

+: corrisponde a una o più ripetizioni del carattere precedente. Immagino che tu non accetti l'input vuoto. Ma se lo fai, passa a *.

^: corrisponde all'avvio della stringa.

$: corrisponde alla fine della stringa.

Hai bisogno di questi due caratteri speciali poiché devi evitare il seguente caso:

&&&PATTERN&&PATTERN

Probabilmente il pattern che non vuoi potrebbe stare tra i pattern che desideri. 

Per questa istanza: &&& non è il caso che ci si aspetta ma la stringa legal è accettabile. Se non aggiungi ^ e $ all'espressione regolare, questo modello corrisponderà al modello sbagliato.

0
Alston

Ecco qualcosa basato sull'approccio ingenuo di Jerub (le sue parole sono ingenue, non mie!):

import string
ALLOWED = frozenset(string.ascii_letters + string.digits + '_' + '-')

def check(mystring):
    return all(c in ALLOWED for c in mystring)

Se ALLOWED fosse una stringa, penso che c in ALLOWED implicherebbe l'iterazione su ogni carattere nella stringa fino a quando non ha trovato una corrispondenza o ha raggiunto la fine. Che, per citare Joel Spolsky, è qualcosa di un algoritmo Shlemiel the Painter .

Ma il test di esistenza in un set dovrebbe essere più efficiente, o almeno meno dipendente dal numero di caratteri consentiti. Certamente questo approccio è un po 'più veloce sulla mia macchina. È chiaro e penso che funzioni abbastanza bene per la maggior parte dei casi (sulla mia macchina lenta posso convalidare decine di migliaia di stringhe short-ish in una frazione di secondo). Mi piace.

REALLY sulla mia macchina una regexp funziona molto più velocemente, ed è semplice come questa (probabilmente più semplice). Quindi questa è probabilmente la migliore strada da seguire.

0
MB.