web-dev-qa-db-fra.com

En Python, comment vérifier si une chaîne de caractères ne contient que certains caractères?

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<"
'''
44
e70

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
30
John Machin

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
55
John Millikin

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.

26
Mark Rushakoff

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

13
Nadia Alramli

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ù:

appeler
Le nombre de fois que cette fonction a été appelée
jusqu'à l'heure
le temps total passé dans la fonction donnée, à l'exclusion du temps consacré aux sous-fonctions
percall
le quotient de tottime divisé par ncalls
cumtime
le temps cumulé passé dans cette et toutes les sous-fonctions
percall
le quotient de cumtime divisé par des appels primitifs

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

2
KingRadical

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.

0
remort