Come posso verificare che una stringa contenga solo lettere, numeri, trattini bassi e trattini?


86

So come farlo se itero tutti i caratteri nella stringa, ma cerco un metodo più elegante.


5
Stai parlando di caratteri ascii, specifici per località o unicode?
jfs

Risposte:


122

Un'espressione regolare farà il trucco con pochissimo codice:

import re

...

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

25
Potresti semplificarlo in: ^ [\ w \ d _-] * $
Prestaul

13
Questa soluzione corrisponderà alle stringhe di lunghezza zero. usa + invece di * per far corrispondere stringhe di 1 o più caratteri.
Jerub

10
@Prestaul: \winclude \de _, quindi isvalid = re.match(r'[\w-]+$', astr)o isinvalid = re.search(r'[^\w-]', astr). Una possibile presenza di locale.setlocaleo stringhe Unicode richiede un'ulteriore considerazione.
jfs

1
Correzione: isvalid = re.match(r'[\w-]*$', astr)- le stringhe vuote sono valide.
jfs

Come puoi anche consentire un punto / punto (.) In quella regex? Modifica, ecco come fare: ^ [a-zA-Z0-9 -_ \ s \.] + $
fredrik

24

[Modifica] C'è un'altra soluzione non ancora menzionata, e nella maggior parte dei casi sembra superare le altre date finora.

Usa string.translate per sostituire tutti i caratteri validi nella stringa e controlla se ne sono rimasti di quelli non validi. Questo è abbastanza veloce in quanto utilizza la funzione C sottostante per fare il lavoro, con pochissimo bytecode Python coinvolto.

Ovviamente le prestazioni non sono tutto: scegliere le soluzioni più leggibili è probabilmente l'approccio migliore quando non ci si trova in un codepath critico per le 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 utilizza 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, drammaticamente con stringhe valide lunghe, ma è battuto dalle espressioni regolari in test_long_invalid (presumibilmente perché la regex può salvarsi immediatamente, ma tradurre deve sempre scansionare l'intera stringa). Gli approcci impostati sono generalmente i peggiori, battendo le espressioni regolari solo per il caso di stringa vuota.

L'uso di all (x in allowed_set per x in s) funziona bene se esce presto, ma può essere negativo se deve scorrere ogni carattere. isSubSet e set difference sono confrontabili 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 funziona un po 'meglio quando si controlla una stringa lunga ma completamente valida, ma peggio per i caratteri non validi vicino alla fine della stringa.


1
Usa string.ascii_lettersinvece di string.lettersse non usi il flag re.LOCALE per le espressioni regolari (altrimenti potresti ottenere risultati falsi positivi in check_trans(). string.maketrans()Non funzionerà per le stringhe unicode.
jfs

Per Python 3 / Unicode / from __future__ import unicode_literals), usa trans_table3 = dict((ord(char), '') for char in allowed_chars)e def check_trans(s): return not s.translate(trans_table3). Ma in generale, ha prestazioni peggiori rispetto alle versioni RE.
Hugo

14

Esistono diversi modi per raggiungere questo obiettivo, alcuni sono più chiari di altri. Per ciascuno dei miei esempi, "True" significa che la stringa passata è valida, "False" 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 verrà utilizzato come delimitatore di "intervallo". Nota anche il $ che significa "fine della stringa". Altre risposte annotate in questa domanda utilizzano una classe di caratteri speciale, '\ w', preferisco sempre usare un intervallo di classi di caratteri esplicito usando [] perché è più facile da capire senza dover cercare una guida di riferimento rapido e più facile da speciale- Astuccio.

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

Un'altra soluzione ha notato che puoi fare una corrispondenza inversa con le espressioni regolari, l'ho inclusa qui ora. Nota che [^ ...] inverte la classe del carattere perché viene utilizzato ^:

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 con un set contenente o a) niente, oppure b) i caratteri offensivi dalla stringa:

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

Nel tuo primo test regex, non dovrebbe "[a-zA-Z0-9 _-] + $" essere "[a-zA-Z0-9 _-] * $". La stringa vuota dovrebbe probabilmente essere considerata come corrispondente.
Brian

Da utilizzare string.ascii_lettersse si utilizzano espressioni regolari "[a-zA-Z]".
jfs


4

In alternativa all'uso di regex potresti farlo in Sets:

from sets import Set

allowed_chars = Set('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-')

if Set(my_little_sting).issubset(allowed_chars):
    # your action
    print True

3
 pat = re.compile ('[^\w-]')

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

1

L'espressione regolare può essere molto flessibile.

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

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

Quindi è necessario aggiungere -char per giustificare il trattino char.

+: Corrisponde a una o più ripetizioni del carattere precedente. Immagino che tu non accetti input in bianco. Ma se lo fai, passa a *.

^: Corrisponde all'inizio della stringa.

$: Corrisponde alla fine della stringa.

Questi due caratteri speciali sono necessari poiché è necessario evitare il seguente caso. I caratteri indesiderati come &qui potrebbero apparire tra il modello corrispondente.

&&&PATTERN&&PATTERN


0

Bene, puoi chiedere l'aiuto di regex, il grande 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 sia di aiuto :)


-1

Puoi sempre usare una comprensione dell'elenco 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])


Si prega di testare il codice prima di pubblicare. Una soluzione basata sulla risposta errata che viene eseguita è: all (c in string.letters + string.digits + "_" per c in mystring)
Jerub

2
Sarà molto più dispendioso in termini di risorse di una regex. Sta facendo una scansione lineare per ogni personaggio (meglio costruire un set prima del tempo) e stai costruendo inutilmente un elenco quando una comprensione del generatore sarebbe più leggera.
Brian

-1

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

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

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

Se ALLOWEDfosse una stringa, penso c in ALLOWEDche comporterebbe l'iterazione su ogni carattere nella stringa fino a quando non trova una corrispondenza o raggiunge la fine. Il che, per citare Joel Spolsky, è una specie di algoritmo di Shlemiel the Painter .

Ma testare l'esistenza in un insieme dovrebbe essere più efficiente, o almeno meno dipendente dal numero di caratteri consentiti. Sicuramente 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 corte in una frazione di secondo). Mi piace.

ATTUALMENTE sulla mia macchina una regexp funziona molte volte più velocemente ed è altrettanto semplice (probabilmente più semplice). Quindi questa probabilmente è la migliore via da seguire.


-4

usa una regex e vedi se corrisponde!

([a-z][A-Z][0-9]\_\-)*

1
Tutti questi personaggi devono essere in una classe, altrimenti otterrai falsi negativi. Inoltre hai dimenticato di includere i marcatori di inizio e fine stringa ... in questo modo, corrisponderà sempre fintanto che è presente un carattere valido.
Thomas

1
Questo effettivamente corrisponderà anche se non ci sono caratteri validi. Corrispondenza di lunghezza zero. Inoltre, non è in Python.
Jerub
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.