it-swarm.com.de

Wie kann man in Python prüfen, ob eine Zeichenfolge nur bestimmte Zeichen enthält?

Wie kann man in Python prüfen, ob eine Zeichenfolge nur bestimmte Zeichen enthält?

Ich muss einen String prüfen, der nur a..z, 0..9 und. (Punkt) und kein anderer Charakter.

Ich könnte über jedes Zeichen iterieren und überprüfen, ob das Zeichen ein..z oder 0..9 oder ist. aber das wäre langsam.

Mir ist jetzt nicht klar, wie man das mit einem regulären Ausdruck macht.

Ist das richtig? Können Sie einen einfacheren regulären Ausdruck oder einen effizienteren Ansatz vorschlagen?.

#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='_-/>"[email protected]#12345abcde<')

'''
Output:
>>> 
Valid   : "abcde.1"
Invalid : "abcde.1#"
Invalid : "ABCDE.12"
Invalid : "_-/>"[email protected]#12345abcde<"
'''
44
e70

Finale (?) Bearbeitung

Antwort, verpackt in einer Funktion, mit kommentierter interaktiver Sitzung:

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

Hinweis: Es gibt einen Vergleich mit der Verwendung von re.match () weiter unten in dieser Antwort. Weitere Timings zeigen, dass match () mit viel längeren Saiten gewinnen würde; match () scheint einen viel größeren Aufwand zu haben als search (), wenn die endgültige Antwort "True" ist. Dies ist rätselhaft (möglicherweise sind es die Kosten für die Rückgabe eines MatchObjects anstelle von None) und kann ein weiteres Durchsuchen rechtfertigen.

==== Earlier text ====

Die bisher akzeptierte Antwort könnte einige Verbesserungen erfordern:

(1) Die Präsentation gibt den Anschein, dass sie das Ergebnis einer interaktiven Python-Sitzung ist:

reg=re.compile('^[a-z0-9\.]+$')
>>>reg.match('jsdlfjdsf12324..3432jsdflsdf')
True

aber match () gibt nicht True zurück

(2) Bei Verwendung von match () ist der ^ am Anfang des Musters redundant und scheint etwas langsamer zu sein als das gleiche Muster ohne den ^

(3) Sollte die Verwendung der Rohsaite automatisch und ohne Rücksicht auf ein neues Muster fördern

(4) Der umgekehrte Schrägstrich vor dem Punkt/Punkt ist redundant

(5) langsamer als der OP-Code!  

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) Kann die falsche Antwort liefern !!

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

Hier ist eine einfache, reine Python-Implementierung. Es sollte verwendet werden, wenn die Leistung nicht kritisch ist (für zukünftige Googler enthalten).

import string
allowed = set(string.ascii_lowercase + string.digits + '.')

def check(test_str):
    set(test_str) <= allowed

In Bezug auf die Leistung wird die Iteration wahrscheinlich die schnellste Methode sein. Regexes müssen eine Zustandsmaschine durchlaufen, und die Gleichheitslösungslösung muss einen temporären Satz erstellen. Es ist jedoch unwahrscheinlich, dass der Unterschied eine große Rolle spielt. Wenn die Leistung dieser Funktion sehr wichtig ist, schreiben Sie sie als C-Erweiterungsmodul mit einer switch-Anweisung (die zu einer Sprungtabelle kompiliert wird).

Hier ist eine C-Implementierung, die if-Anweisungen aufgrund von Platzbeschränkungen verwendet. Wenn Sie das kleine bisschen zusätzliche Geschwindigkeit unbedingt benötigen, schreiben Sie den Switch-Case heraus. In meinen Tests ist es sehr gut (2 Sekunden vs. 9 Sekunden in Benchmarks gegen den 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);
}

Fügen Sie es in Ihre setup.py ein:

from distutils.core import setup, Extension
ext_modules = [
    Extension ('stringcheck', ['stringcheck.c']),
],

Benutzen als:

>>> from stringcheck import check
>>> check("abc")
True
>>> check("ABC")
False
55
John Millikin

Einfacher Ansatz? Ein bisschen mehr Pythonic?

>>> ok = "0123456789abcdef"
>>> all(c in ok for c in "123456abc")
True
>>> all(c in ok for c in "hello world")
False

Es ist sicherlich nicht das effizienteste, aber es ist sicher lesbar.

26
Mark Rushakoff

EDIT: Der reguläre Ausdruck wurde geändert, um A-Z auszuschließen.

Die Lösung für reguläre Ausdrücke ist bisher die schnellste reine Python-Lösung

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

Im Vergleich zu anderen Lösungen:

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

Wenn Sie leere Zeichenfolgen zulassen möchten, ändern Sie sie in:

reg=re.compile('^[a-z0-9\.]*$')
>>>reg.match('')
False

Auf Anfrage werde ich den anderen Teil der Antwort zurücksenden. Beachten Sie jedoch, dass die folgenden A-Z-Bereiche akzeptiert werden.

Sie können isalnum verwenden.

test_str.replace('.', '').isalnum()

>>> 'test123.3'.replace('.', '').isalnum()
True
>>> 'test123-3'.replace('.', '').isalnum()
False

EDIT Die Verwendung von isalnum ist wesentlich effizienter als die eingestellte Lösung

>>> timeit.Timer("'jsdlfjdsf12324..3432jsdflsdf'.replace('.', '').isalnum()").timeit()
0.63245487213134766

EDIT2 John gab ein Beispiel, bei dem das Obige nicht funktioniert. Ich habe die Lösung geändert, um diesen Sonderfall mit encode zu überwinden

test_str.replace('.', '').encode('ascii', 'replace').isalnum()

Und es ist immer noch fast dreimal schneller als die eingestellte Lösung

timeit.Timer("u'ABC\u0131\u0661'.encode('ascii', 'replace').replace('.','').isalnum()", "import string; allowed = set(string.ascii_lowercase + string.digits + '.')").timeit()
1.5719811916351318

Meiner Meinung nach ist die Verwendung regulärer Ausdrücke das Beste, um dieses Problem zu lösen

13
Nadia Alramli

Dies wurde bereits zufriedenstellend beantwortet, aber für die Leute, die nach dieser Tatsache auf dieses Problem stoßen, habe ich einige verschiedene Methoden zur Erreichung dieser Ziele erstellt. In meinem Fall wollte ich Hexadezimalzeichen in Großbuchstaben. Ändern Sie sie bei Bedarf entsprechend Ihren Bedürfnissen.

Hier sind meine Testimplementierungen:

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

Und die Tests in Python 3.4.0 unter 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_*")

was zu folgenden Ergebnissen führte:

 50335004 Funktionsaufrufe in 13.428 Sekunden 

 Sortiert nach: Gesamtzeit, Funktionsname 
 Die Liste wurde aufgrund der Einschränkung von 20 auf 8 reduziert 

 ncalls tottime percall cumtime percall Dateiname: Leineno (Funktion) 
 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) 

woher:

nennungen
Die Anzahl der Aufrufe dieser Funktion
tottime
die Gesamtzeit, die in der angegebenen Funktion verbracht wurde, mit Ausnahme der Zeit, die für Unterfunktionen verwendet wird
per Anruf
der Quotient von tottime geteilt durch ncalls
cumtime
die kumulierte Zeit in dieser und allen Teilfunktionen
per Anruf
der Quotient von cumtime geteilt durch primitive Aufrufe

Die Spalten, für die wir uns eigentlich interessieren, sind cumtime und percall, da dies die tatsächliche Zeit zeigt, die vom Funktionseintrag bis zum Beenden benötigt wird. Wie wir sehen können, sind Regex-Übereinstimmung und Suche nicht sehr unterschiedlich. 

Es ist schneller, den Regex nicht zu kompilieren, wenn Sie ihn jedes Mal kompiliert hätten. Es ist etwa 7,5% schneller als einmal zu kompilieren, aber nur 2,5% schneller als nicht zu kompilieren.

test_set war doppelt so langsam wie re_search und dreimal so langsam wie re_match

test_not_any war eine volle Größenordnung langsamer als test_set

TL; DR: Verwenden Sie re.match oder re.search

2
KingRadical

Verwenden Sie Python-Sets, wenn Sie hm ... Datensätze vergleichen müssen. Strings können sehr schnell als Gruppen von Zeichen dargestellt werden. Hier teste ich ob die Telefonnummer erlaubt ist. Der erste String ist erlaubt, der zweite nicht. Funktioniert schnell und einfach.

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

Verwenden Sie niemals Regexx, wenn Sie sie vermeiden können.

0
remort