web-dev-qa-db-fra.com

Comment vérifier qu'une chaîne ne contient que des lettres, des chiffres, des tirets bas et des tirets?

Je sais comment faire cela si je parcours tous les caractères de la chaîne mais je recherche une méthode plus élégante.

76
Ethan Post

Une expression régulière fera l'affaire avec très peu de code:

import re

...

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

[Modifier] Il existe une autre solution qui n'a pas encore été mentionnée et qui semble surperformer les autres données jusqu'à présent dans la plupart des cas.

Utilisez string.translate pour remplacer tous les caractères valides de la chaîne et voir s’il en reste des non valides. Ceci est assez rapide car il utilise la fonction C sous-jacente pour effectuer le travail, avec très peu de bytecode python impliqué.

De toute évidence, les performances ne sont pas tout. Pour les solutions les plus lisibles, la meilleure approche est probablement la meilleure lorsque vous ne vous trouvez pas dans un chemin de code critique pour la performance, mais pour voir comment les solutions se superposent, voici une comparaison des performances de toutes les méthodes proposées jusqu'à présent. check_trans est celui qui utilise la méthode string.translate.

Code de test:

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

Les résultats sur mon système sont:

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’approche de la traduction semble meilleure dans la plupart des cas, de façon spectaculaire avec de longues chaînes valides, mais elle est dépassée par les expressions rationnelles de test_long_invalid (sans doute parce que l’expression rationnelle peut se décharger immédiatement, mais translate doit toujours balayer toute la chaîne). Les approches définies sont généralement les pires, ne battant que les expressions rationnelles pour le cas de la chaîne vide.

Utiliser tout (x dans allowed_set pour x in s) donne de bons résultats s’il échoue tôt, mais peut être mauvais s’il doit parcourir tous les caractères. isSubSet et set difference sont comparables et sont toujours proportionnels à la longueur de la chaîne, quelles que soient les données.

Il existe une différence similaire entre les méthodes regex correspondant à tous les caractères valides et la recherche de caractères non valides. La correspondance fonctionne un peu mieux lors de la vérification d'une chaîne longue, mais entièrement valide, mais pire pour les caractères non valides à la fin de la chaîne.

22
Brian

Il y a différentes façons d'atteindre cet objectif, certaines sont plus claires que d'autres. Pour chacun de mes exemples, "True" signifie que la chaîne transmise est valide, "False" signifie qu'elle contient des caractères non valides.

Tout d'abord, il y a l'approche naïve:

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

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

Ensuite, il y a utilisation d'une expression régulière, vous pouvez le faire avec re.match (). Notez que '-' doit être à la fin du [], sinon il sera utilisé comme un séparateur de 'plage'. Notez également le $ qui signifie «fin de chaîne». Les autres réponses notées dans cette question utilisent une classe de caractères spéciale, '\ w', je préfère toujours utiliser une plage de classes de caractères explicite en utilisant [] car il est plus facile à comprendre sans avoir à chercher un guide de référence rapide, et plus facile Cas.

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

Une autre solution a noté que vous pouvez faire une correspondance inverse avec des expressions régulières, je l’ai incluse ici maintenant. Notez que [^ ...] inverse la classe de caractères car le ^ est utilisé:

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

Vous pouvez également faire quelque chose de délicat avec l'objet 'set'. Jetez un œil à cet exemple, qui supprime de la chaîne d'origine tous les caractères autorisés, nous laissant ainsi un ensemble contenant soit a) rien, soit b) les caractères incriminés de la chaîne:

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

Si ce n’était les tirets et les soulignés, la solution la plus simple serait de

my_little_string.isalnum()

(Section 3.6.1 de la référence de la bibliothèque Python)

11
Ber

Au lieu d'utiliser regex, vous pouvez le faire dans des ensembles:

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

Eh bien, vous pouvez demander l'aide de regex, le grand ici :)

code:

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'

Sortie:

yes  

J'espère que cela t'aides :)

1

Vous pouvez toujours utiliser une compréhension de liste et vérifier les résultats avec tous, ce serait un peu moins gourmand en ressources que d'utiliser un regex: all([c in string.letters + string.digits + ["_", "-"] for c in mystring])

0
William Keller

L'expression régulière peut être très flexible. 

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

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

Donc, vous devez ajouter - char.

+: correspond à une ou plusieurs répétitions du caractère précédent. Je suppose que vous n'acceptez pas les entrées vierges. Mais si vous le faites, passez à *.

^: correspond au début de la chaîne.

$: correspond à la fin de la chaîne.

Vous avez besoin de ces deux caractères spéciaux car vous devez éviter le cas suivant:

&&&PATTERN&&PATTERN

Le motif que vous ne voulez pas pourrait probablement rester entre les motifs que vous voulez. 

Pour cette instance: &&& n'est pas le cas que vous attendez, mais la chaîne legal est acceptable. Si vous n'ajoutez pas ^ et $ à l'expression régulière, ce modèle correspondra au modèle incorrect.

0
Alston

Voici quelque chose basé sur "l'approche naïve" de Jerub (ses mots sont naïfs, pas les miens!):

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

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

Si ALLOWED était une chaîne, alors je pense que c in ALLOWED impliquerait une itération sur chaque caractère de la chaîne jusqu'à ce qu'il trouve une correspondance ou atteigne la fin. Ce qui, pour citer Joel Spolsky, est en quelque sorte un algorithme de Shlemiel the Painter .

Mais tester l'existence dans un ensemble devrait être plus efficace, ou du moins moins dépendre du nombre de caractères autorisés. Cette approche est certainement un peu plus rapide sur ma machine. C'est clair et je pense que cela fonctionne assez bien dans la plupart des cas (sur ma machine lente, je peux valider des dizaines de milliers de chaînes courtes en une fraction de seconde). Je l'aime.

EN FAIT sur ma machine, une expression rationnelle est plusieurs fois plus rapide et aussi simple que cela (sans doute plus simple). C'est donc probablement la meilleure voie à suivre.

0
MB.