94 votes

Comment vérifier qu'une chaîne de caractères ne contient que des lettres, des chiffres, des traits de soulignement et des tirets ?

Je sais comment faire si j'itère sur tous les caractères de la chaîne, mais je cherche une méthode plus élégante.

6 votes

Parlez-vous de lettres ascii, locales ou unicode ?

134voto

Thomas Points 63635

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

27 votes

Vous pourriez simplifier ça en disant : ^[ \w\d_ -]*$

16 votes

Cette solution correspondra à des chaînes de longueur nulle. Utilisez + au lieu de * pour qu'elle corresponde à des chaînes de 1 ou plusieurs caractères.

12 votes

@Prestaul : \w comprend \d y _ donc isvalid = re.match(r'[\w-]+$', astr) o isinvalid = re.search(r'[^\w-]', astr) . Une présence possible de locale.setlocale ou des chaînes unicode nécessite un examen supplémentaire.

25voto

Brian Points 48423

[Il existe une autre solution qui n'a pas encore été mentionnée et qui semble être plus efficace que les autres dans la plupart des cas.

Utilisez string.translate pour remplacer tous les caractères valides de la chaîne, et voyez s'il reste des caractères invalides. Cette méthode est assez rapide car elle utilise la fonction C sous-jacente pour faire le travail, avec très peu de bytecode python impliqué.

Évidemment, les performances ne sont pas tout - rechercher les solutions les plus lisibles est probablement la meilleure approche lorsqu'il ne s'agit pas d'un chemin de code critique pour les performances, mais juste pour voir comment les solutions se comparent, voici une comparaison des performances de toutes les méthodes proposées jusqu'à présent. check_trans est celle 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 les suivants :

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 translate semble être la meilleure dans la plupart des cas, elle l'est encore plus avec les longues chaînes valides, mais elle est battue par les regex dans test_long_invalid (probablement parce que le regex peut sortir immédiatement, alors que translate doit toujours analyser la chaîne entière). Les approches par ensembles sont généralement les pires, ne battant les regex que dans le cas de la chaîne vide.

L'utilisation de all(x in allowed_set for x in s) donne de bons résultats si elle s'arrête tôt, mais peut être mauvaise si elle doit itérer à travers chaque caractère. isSubSet et set difference sont comparables, et sont systématiquement proportionnels à la longueur de la chaîne, quelles que soient les données.

Il y a une différence similaire entre les méthodes regex qui correspondent à tous les caractères valides et celles qui recherchent les caractères non valides. La mise en correspondance fonctionne un peu mieux lors de la vérification d'une chaîne longue mais entièrement valide, mais moins bien pour les caractères non valides proches de la fin de la chaîne.

1 votes

Utilice string.ascii_letters au lieu de string.letters si vous n'utilisez pas l'indicateur re.LOCALE pour les regexps (sinon vous risquez d'obtenir des résultats faux positifs en check_trans() . string.maketrans() ne fonctionnera pas pour les chaînes unicode.

1 votes

Pour Python 3/Unicode/ from __future__ import unicode_literals ), utilisez trans_table3 = dict((ord(char), '') for char in allowed_chars) et def check_trans(s): return not s.translate(trans_table3) . Mais en général, ses performances sont moins bonnes que celles des versions RE.

15voto

Jerub Points 17488

Il existe plusieurs façons d'atteindre cet objectif, certaines sont plus claires que d'autres. Pour chacun de mes exemples, "Vrai" signifie que la chaîne passée est valide, "Faux" signifie qu'elle contient des caractères invalides.

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 l'utilisation d'une expression régulière, vous pouvez le faire avec re.match(). Notez que le '-' doit se trouver à la fin de [], sinon il sera utilisé comme délimiteur d'intervalle. Notez également le $ qui signifie 'fin de chaîne'. D'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 c'est plus facile à comprendre sans avoir à consulter un guide de référence rapide, et plus facile à mettre en majuscules.

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". Regardez cet exemple, qui supprime de la chaîne de caractères d'origine tous les caractères autorisés, nous laissant avec un ensemble contenant soit a) rien, soit b) les caractères incriminés de la chaîne de caractères :

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

0 votes

Dans votre premier test regex, "[a-zA-Z0-9_-]+$" ne devrait pas être "[a-zA-Z0-9_-]*$". La chaîne vide devrait probablement être considérée comme une correspondance.

0 votes

Utilice string.ascii_letters si vous utilisez des regexps '[a-zA-Z]'.

13voto

Ber Points 10364

S'il n'y avait pas les tirets et les traits de soulignement, la solution la plus simple serait la suivante

my_little_string.isalnum()

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

0 votes

Malheureusement, le lien ne fonctionne plus, mais voici la section concernée. Python " 3.3.6 Documentation " La bibliothèque standard de Python " 4.7.1. Méthodes pour les chaînes de caractères . Merci @Ber, c'est exactement ce dont j'avais besoin.

3voto

Javier Points 33134
 pat = re.compile ('[^\w-]')

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X