En Python, comment vérifier si une chaîne de caractères ne contient que certains caractères?
Je dois vérifier une chaîne ne contenant que a..z, 0..9 et. (période) et pas d'autre personnage.
Je pourrais parcourir chaque caractère et vérifier que le caractère est a..z ou 0..9, ou. mais ce serait lent.
Je ne sais pas maintenant comment le faire avec une expression régulière.
Est-ce correct? Pouvez-vous suggérer une expression régulière plus simple ou une approche plus efficace?.
#Valid chars . a-z 0-9
def check(test_str):
import re
#http://docs.python.org/library/re.html
#re.search returns None if no position in the string matches the pattern
#pattern to search for any character other then . a-z 0-9
pattern = r'[^\.a-z0-9]'
if re.search(pattern, test_str):
#Character other then . a-z 0-9 was found
print 'Invalid : %r' % (test_str,)
else:
#No character other then . a-z 0-9 was found
print 'Valid : %r' % (test_str,)
check(test_str='abcde.1')
check(test_str='abcde.1#')
check(test_str='ABCDE.12')
check(test_str='_-/>"!@#12345abcde<')
'''
Output:
>>>
Valid : "abcde.1"
Invalid : "abcde.1#"
Invalid : "ABCDE.12"
Invalid : "_-/>"!@#12345abcde<"
'''
Final (?) Modifier
Répondez, encapsulé dans une fonction, avec session interactive annotée:
>>> import re
>>> def special_match(strg, search=re.compile(r'[^a-z0-9.]').search):
... return not bool(search(strg))
...
>>> special_match("")
True
>>> special_match("az09.")
True
>>> special_match("az09.\n")
False
# The above test case is to catch out any attempt to use re.match()
# with a `$` instead of `\Z` -- see point (6) below.
>>> special_match("az09.#")
False
>>> special_match("az09.X")
False
>>>
Remarque: Il existe une comparaison avec l'utilisation de re.match () plus loin dans cette réponse. D'autres timings montrent que match () gagnerait avec des chaînes beaucoup plus longues; match () semble avoir une charge beaucoup plus importante que search () lorsque la réponse finale est True; c'est curieux (c'est peut-être le coût de retourner un MatchObject au lieu de Aucun) et cela peut justifier de fouiller davantage.
==== Earlier text ====
La réponse [précédemment] acceptée pourrait utiliser quelques améliorations:
(1) La présentation donne l’apparence d’être le résultat d’une session interactive Python:
reg=re.compile('^[a-z0-9\.]+$')
>>>reg.match('jsdlfjdsf12324..3432jsdflsdf')
True
mais match () ne retourne pas True
(2) Pour une utilisation avec match (), le ^
au début du motif est redondant et semble être légèrement plus lent que le même motif sans le ^
(3) Devrait encourager l'utilisation de la chaîne brute automatiquement sans réfléchir pour tout re motif
(4) La barre oblique inverse devant le point/la période est redondante
(5) Plus lent que le code de l'OP!
Prompt>rem OP's version -- NOTE: OP used raw string!
Prompt>\python26\python -mtimeit -s"t='jsdlfjdsf12324..3432jsdflsdf';import
re;reg=re.compile(r'[^a-z0-9\.]')" "not bool(reg.search(t))"
1000000 loops, best of 3: 1.43 usec per loop
Prompt>rem OP's version w/o backslash
Prompt>\python26\python -mtimeit -s"t='jsdlfjdsf12324..3432jsdflsdf';import
re;reg=re.compile(r'[^a-z0-9.]')" "not bool(reg.search(t))"
1000000 loops, best of 3: 1.44 usec per loop
Prompt>rem cleaned-up version of accepted answer
Prompt>\python26\python -mtimeit -s"t='jsdlfjdsf12324..3432jsdflsdf';import
re;reg=re.compile(r'[a-z0-9.]+\Z')" "bool(reg.match(t))"
100000 loops, best of 3: 2.07 usec per loop
Prompt>rem accepted answer
Prompt>\python26\python -mtimeit -s"t='jsdlfjdsf12324..3432jsdflsdf';import
re;reg=re.compile('^[a-z0-9\.]+$')" "bool(reg.match(t))"
100000 loops, best of 3: 2.08 usec per loop
(6) Peut produire la mauvaise réponse !!
>>> import re
>>> bool(re.compile('^[a-z0-9\.]+$').match('1234\n'))
True # uh-oh
>>> bool(re.compile('^[a-z0-9\.]+\Z').match('1234\n'))
False
Voici une implémentation simple, en pur Python. Il doit être utilisé lorsque les performances ne sont pas critiques (inclus pour les futurs utilisateurs de Google).
import string
allowed = set(string.ascii_lowercase + string.digits + '.')
def check(test_str):
set(test_str) <= allowed
En ce qui concerne les performances, l'itération sera probablement la méthode la plus rapide. Les expressions rationnelles doivent itérer sur une machine à états et la solution d'égalité des ensembles doit créer un ensemble temporaire. Cependant, il est peu probable que la différence compte beaucoup. Si l'exécution de cette fonction est très importante, écrivez-la en tant que module d'extension C avec une instruction switch (qui sera compilée dans une table de saut).
Voici une implémentation en C, qui utilise des instructions if en raison de contraintes d'espace. Si vous avez absolument besoin d'un peu plus de vitesse, écrivez le boîtier de commutation. Dans mes tests, il fonctionne très bien (2 secondes contre 9 secondes dans les points de repère par rapport à la regex).
#define PY_SSIZE_T_CLEAN
#include <Python.h>
static PyObject *check(PyObject *self, PyObject *args)
{
const char *s;
Py_ssize_t count, ii;
char c;
if (0 == PyArg_ParseTuple (args, "s#", &s, &count)) {
return NULL;
}
for (ii = 0; ii < count; ii++) {
c = s[ii];
if ((c < '0' && c != '.') || c > 'z') {
Py_RETURN_FALSE;
}
if (c > '9' && c < 'a') {
Py_RETURN_FALSE;
}
}
Py_RETURN_TRUE;
}
PyDoc_STRVAR (DOC, "Fast stringcheck");
static PyMethodDef PROCEDURES[] = {
{"check", (PyCFunction) (check), METH_VARARGS, NULL},
{NULL, NULL}
};
PyMODINIT_FUNC
initstringcheck (void) {
Py_InitModule3 ("stringcheck", PROCEDURES, DOC);
}
Incluez-le dans votre setup.py:
from distutils.core import setup, Extension
ext_modules = [
Extension ('stringcheck', ['stringcheck.c']),
],
Utilisé comme:
>>> from stringcheck import check
>>> check("abc")
True
>>> check("ABC")
False
Approche plus simple? Un peu plus de Pythonic?
>>> ok = "0123456789abcdef"
>>> all(c in ok for c in "123456abc")
True
>>> all(c in ok for c in "hello world")
False
Ce n'est certainement pas le plus efficace, mais c'est certainement lisible.
EDIT: modification de l'expression régulière pour exclure A-Z
La solution d'expression régulière est la solution pure en python la plus rapide à ce jour
reg=re.compile('^[a-z0-9\.]+$')
>>>reg.match('jsdlfjdsf12324..3432jsdflsdf')
True
>>> timeit.Timer("reg.match('jsdlfjdsf12324..3432jsdflsdf')", "import re; reg=re.compile('^[a-z0-9\.]+$')").timeit()
0.70509696006774902
Par rapport à d'autres solutions:
>>> timeit.Timer("set('jsdlfjdsf12324..3432jsdflsdf') <= allowed", "import string; allowed = set(string.ascii_lowercase + string.digits + '.')").timeit()
3.2119350433349609
>>> timeit.Timer("all(c in allowed for c in 'jsdlfjdsf12324..3432jsdflsdf')", "import string; allowed = set(string.ascii_lowercase + string.digits + '.')").timeit()
6.7066690921783447
Si vous voulez autoriser les chaînes vides, changez-le en:
reg=re.compile('^[a-z0-9\.]*$')
>>>reg.match('')
False
Sur demande, je vais vous renvoyer l’autre partie de la réponse. Mais s'il vous plaît noter que ce qui suit accepte A-Z gamme.
Vous pouvez utiliser isalnum
test_str.replace('.', '').isalnum()
>>> 'test123.3'.replace('.', '').isalnum()
True
>>> 'test123-3'.replace('.', '').isalnum()
False
EDIT Utiliser isalnum est beaucoup plus efficace que la solution choisie
>>> timeit.Timer("'jsdlfjdsf12324..3432jsdflsdf'.replace('.', '').isalnum()").timeit()
0.63245487213134766
EDIT2 John a donné un exemple où ce qui précède ne fonctionne pas. J'ai changé la solution pour surmonter ce cas particulier en utilisant l'encodage
test_str.replace('.', '').encode('ascii', 'replace').isalnum()
Et c'est toujours presque 3 fois plus rapide que la solution d'ensemble
timeit.Timer("u'ABC\u0131\u0661'.encode('ascii', 'replace').replace('.','').isalnum()", "import string; allowed = set(string.ascii_lowercase + string.digits + '.')").timeit()
1.5719811916351318
À mon avis, utiliser des expressions régulières est le meilleur moyen de résoudre ce problème
On a déjà répondu de manière satisfaisante à cette question, mais pour les personnes qui en ont eu connaissance après coup, j’ai fait le profil de différentes méthodes pour y parvenir. Dans mon cas, je voulais des chiffres hexadécimaux majuscules, donc modifiez-les au besoin.
Voici mes implémentations de test:
import re
hex_digits = set("ABCDEF1234567890")
hex_match = re.compile(r'^[A-F0-9]+\Z')
hex_search = re.compile(r'[^A-F0-9]')
def test_set(input):
return set(input) <= hex_digits
def test_not_any(input):
return not any(c not in hex_digits for c in input)
def test_re_match1(input):
return bool(re.compile(r'^[A-F0-9]+\Z').match(input))
def test_re_match2(input):
return bool(hex_match.match(input))
def test_re_match3(input):
return bool(re.match(r'^[A-F0-9]+\Z', input))
def test_re_search1(input):
return not bool(re.compile(r'[^A-F0-9]').search(input))
def test_re_search2(input):
return not bool(hex_search.search(input))
def test_re_search3(input):
return not bool(re.match(r'[^A-F0-9]', input))
Et les tests, en Python 3.4.0 sur Mac OS X:
import cProfile
import pstats
import random
# generate a list of 10000 random hex strings between 10 and 10009 characters long
# this takes a little time; be patient
tests = [ ''.join(random.choice("ABCDEF1234567890") for _ in range(l)) for l in range(10, 10010) ]
# set up profiling, then start collecting stats
test_pr = cProfile.Profile(timeunit=0.000001)
test_pr.enable()
# run the test functions against each item in tests.
# this takes a little time; be patient
for t in tests:
for tf in [test_set, test_not_any,
test_re_match1, test_re_match2, test_re_match3,
test_re_search1, test_re_search2, test_re_search3]:
_ = tf(t)
# stop collecting stats
test_pr.disable()
# we create our own pstats.Stats object to filter
# out some stuff we don't care about seeing
test_stats = pstats.Stats(test_pr)
# normally, stats are printed with the format %8.3f,
# but I want more significant digits
# so this monkey patch handles that
def _f8(x):
return "%11.6f" % x
def _print_title(self):
print(' ncalls tottime percall cumtime percall', end=' ', file=self.stream)
print('filename:lineno(function)', file=self.stream)
pstats.f8 = _f8
pstats.Stats.print_title = _print_title
# sort by cumulative time (then secondary sort by name), ascending
# then print only our test implementation function calls:
test_stats.sort_stats('cumtime', 'name').reverse_order().print_stats("test_*")
ce qui a donné les résultats suivants:
50335004 appels de fonction en 13,428 secondes Classés par: temps cumulé, nom de la fonction Liste réduite de 20 à 8 en raison de restrictions ncalls tottime percall cumtime percall nom de fichier: lineno (function) 10000 0,005233 0,000001 0,367360 0,000037: 1 (test_re_match2) 10000 0,006248 0,000001 0,378853 0,000038: 1 (test_re_match3) 10000 0,010710 0,000001 0,395770 0,000040: 1 (test_re_match1) 10000 0,004578 0,000000 0,467386 0,000047: 1 (test_re_search2) 10000 0,005994 0,000001 0,475329 0,000048: 1 (test_re_search3) 10000 0,008100 0,000001 0,482209 0,000048: 1 (test_re_search1) 10000 0,863139 0,000086 0,863139 0,000086: 1 (test_set) 10000 0,007414 0,000001 9,962580 0,000996: 1 (test_not_any)
où:
Les colonnes qui nous intéressent sont cumtime et percall, car cela nous montre le temps réel pris de l’entrée de la fonction à la sortie. Comme nous pouvons le constater, la correspondance des expressions rationnelles et la recherche ne sont pas très différentes.
Il est plus rapide de ne pas prendre la peine de compiler la regex si vous l’aviez toujours compilée. Il est environ 7,5% plus rapide à compiler une fois que chaque fois, mais seulement 2,5% plus rapide à compiler que de ne pas compiler.
test_set était deux fois plus lent que re_search et trois fois plus lent que re_match
test_not_any était un ordre de grandeur complet plus lent que test_set
TL; DR: Utiliser re.match ou re.search
Utilisez python Sets lorsque vous devez comparer hm ... des ensembles de données. Les chaînes peuvent être représentées comme des ensembles de caractères assez rapidement. Ici, je teste si la chaîne est autorisée numéro de téléphone. La première chaîne est autorisée, la seconde non. Fonctionne rapidement et simplement.
In [17]: timeit.Timer("allowed = set('0123456789+-() ');p = set('+7(898) 64-901-63 ');p.issubset(allowed)").timeit()
Out[17]: 0.8106249139964348
In [18]: timeit.Timer("allowed = set('0123456789+-() ');p = set('+7(950) 64-901-63 фыв');p.issubset(allowed)").timeit()
Out[18]: 0.9240323599951807
Ne jamais utiliser les expressions rationnelles si vous pouvez les éviter.