Ich weiß, wie das geht, wenn ich alle Zeichen in der Zeichenfolge durchlaufe, aber ich suche nach einer eleganteren Methode.
Ein regulärer Ausdruck macht den Trick mit sehr wenig Code:
import re
...
if re.match("^[A-Za-z0-9_-]*$", my_little_string):
# do something here
[Bearbeiten] Es gibt noch eine andere Lösung, die bisher noch nicht erwähnt wurde, und scheint in den meisten Fällen besser zu sein als die andere.
Verwenden Sie string.translate, um alle gültigen Zeichen in der Zeichenfolge zu ersetzen, und prüfen Sie, ob noch ungültige Zeichen vorhanden sind. Dies ist ziemlich schnell, da die zugrunde liegende C-Funktion für die Arbeit verwendet wird, wobei sehr wenig Python-Bytecode involviert ist.
Natürlich ist Leistung nicht alles - die lesbarsten Lösungen zu wählen, ist wahrscheinlich der beste Ansatz, wenn es sich nicht um einen leistungskritischen Codepfad handelt, aber um zu sehen, wie sich die Lösungen stapeln, folgt ein Leistungsvergleich aller bisher vorgeschlagenen Methoden. check_trans verwendet die string.translate-Methode.
Testcode:
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()
Die Ergebnisse meines Systems sind:
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
Der Übersetzungsansatz scheint in den meisten Fällen am besten zu sein, dramatisch so mit langen gültigen Zeichenfolgen. Er wird jedoch von den regulären Ausdrücken in test_long_invalid ausgetragen (vermutlich, weil die reguläre Ausdrücke sofort rausfliegen können. Die eingestellten Ansätze sind normalerweise am schlechtesten und übertreffen Regex nur für den Fall der leeren Zeichenfolge.
Die Verwendung von all (x in allowed_set für x in s) ist gut, wenn es früh abspringt, kann aber schlecht sein, wenn jedes Zeichen durchlaufen werden muss. isSubSet und set difference sind vergleichbar und sind unabhängig von den Daten konsistent proportional zur Länge des Strings.
Es gibt einen ähnlichen Unterschied zwischen den Regex-Methoden, die mit allen gültigen Zeichen übereinstimmen, und nach ungültigen Zeichen suchen. Die Übereinstimmung ist etwas besser, wenn nach einer langen, aber vollständig gültigen Zeichenfolge gesucht wird, aber nach ungültigen Zeichen am Ende der Zeichenfolge schlechter.
Es gibt verschiedene Möglichkeiten, dieses Ziel zu erreichen, einige sind klarer als andere. Für jedes meiner Beispiele bedeutet 'True', dass der übergebene String gültig ist, 'False', dass er ungültige Zeichen enthält.
Zunächst einmal gibt es den naiven Ansatz:
import string
allowed = string.letters + string.digits + '_' + '-'
def check_naive(mystring):
return all(c in allowed for c in mystring)
Dann gibt es einen regulären Ausdruck, den Sie mit re.match () tun können. Beachten Sie, dass '-' am Ende von [] stehen muss, andernfalls wird es als Trennzeichen für 'Range' verwendet. Beachten Sie auch das $, das "Ende der Zeichenfolge" bedeutet. Andere Antworten in dieser Frage verwenden eine spezielle Zeichenklasse "\ w". Ich ziehe es vor, einen expliziten Zeichenklassenbereich mit [] zu verwenden, da dies einfacher zu verstehen ist, ohne dass eine Kurzanleitung nachgeschlagen werden muss. Fall.
import re
CHECK_RE = re.compile('[a-zA-Z0-9_-]+$')
def check_re(mystring):
return CHECK_RE.match(mystring)
Eine andere Lösung stellte fest, dass Sie mit regulären Ausdrücken eine umgekehrte Übereinstimmung herstellen können. Ich habe das hier jetzt hinzugefügt. Beachten Sie, dass [^ ...] die Zeichenklasse invertiert, weil das ^ verwendet wird:
CHECK_INV_RE = re.compile('[^a-zA-Z0-9_-]')
def check_inv_re(mystring):
return not CHECK_INV_RE.search(mystring)
Sie können mit dem 'Set'-Objekt auch etwas Kniffliges tun. Schauen Sie sich dieses Beispiel an, in dem alle Zeichen, die zulässig sind, aus der ursprünglichen Zeichenfolge entfernt werden. Es verbleibt ein Satz, der entweder a) nichts oder b) die störenden Zeichen aus der Zeichenfolge enthält:
def check_set(mystring):
return not set(mystring) - set(allowed)
Ohne Striche und Unterstriche wäre die einfachste Lösung
my_little_string.isalnum()
(Abschnitt 3.6.1 der Python-Bibliotheksreferenz)
Alternativ zur Verwendung von Regex können Sie dies auch in Sets tun:
from sets import Set
allowed_chars = Set('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-')
if Set(my_little_sting).issubset(allowed_chars):
# your action
print True
pat = re.compile ('[^\w-]')
def onlyallowed(s):
return not pat.search (s)
Tja, du kannst die Hilfe von Regex fragen, die großartig hier ist :)
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'
Ausgabe:
yes
Hoffe das hilft :)
Sie können immer ein Listenverständnis verwenden und die Ergebnisse mit allen überprüfen, es wäre etwas weniger Ressourcen als mit einem regulären Ausdruck: all([c in string.letters + string.digits + ["_", "-"] for c in mystring])
Reguläre Ausdrücke können sehr flexibel sein.
import re;
re.fullmatch("^[\w-]+$", target_string) # fullmatch starts from python 3.4 `match` looks also workable here
\w
: Nur [a-zA-Z0-9_]
Sie müssen also -
char hinzufügen.
+
: Entspricht einer oder mehreren Wiederholungen des vorhergehenden Zeichens. Ich denke, Sie akzeptieren keine leeren Eingaben. Wenn Sie dies tun, wechseln Sie zu *
.
^
: Stimmt mit dem Anfang der Zeichenfolge überein.
$
: Stimmt mit dem Ende der Zeichenfolge überein.
Sie benötigen diese beiden Sonderzeichen, da Sie den folgenden Fall vermeiden müssen:
&&&PATTERN&&PATTERN
Das Muster, das Sie nicht möchten, befindet sich möglicherweise zwischen den gewünschten Mustern.
Für diese Instanz gilt Folgendes: &&&
ist nicht der Fall, aber die Zeichenfolge legal
ist zulässig. Wenn Sie dem regulären Ausdruck nicht ^
und $
hinzufügen, stimmt dieses Muster mit dem falschen Muster überein.
Hier ist etwas, das auf Jerubs "naiver Ansatz" basiert (naiv sind seine Worte, nicht meine!):
import string
ALLOWED = frozenset(string.ascii_letters + string.digits + '_' + '-')
def check(mystring):
return all(c in ALLOWED for c in mystring)
Wenn ALLOWED
eine Zeichenfolge wäre, denke ich, würde c in ALLOWED
jedes Zeichen in der Zeichenfolge durchlaufen, bis eine Übereinstimmung gefunden wurde oder das Ende erreicht wurde. Was, um Joel Spolsky zu zitieren, etwas von einem Shlemiel the Painter-Algorithmus ist.
Das Vorhandensein in einer Gruppe sollte jedoch effizienter sein oder zumindest weniger von der Anzahl der zulässigen Zeichen abhängen. Sicher ist dieser Ansatz auf meiner Maschine etwas schneller. Es ist klar und ich denke, dass es für die meisten Fälle eine gute Leistung bringt (auf meinem langsamen Rechner kann ich Zehntausende von kurzen Saiten in einem Sekundenbruchteil überprüfen). Ich mag das.
EIGENTLICH Auf meinem Rechner klappt ein Regex um ein Vielfaches schneller und ist genauso einfach (dies ist vermutlich einfacher). Das ist wahrscheinlich der beste Weg nach vorne.