it-swarm.com.de

Wie überprüfe ich, ob eine Zeichenfolge nur Buchstaben, Zahlen, Unterstriche und Bindestriche enthält?

Ich weiß, wie das geht, wenn ich alle Zeichen in der Zeichenfolge durchlaufe, aber ich suche nach einer eleganteren Methode.

76
Ethan Post

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

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

22
Brian

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

Ohne Striche und Unterstriche wäre die einfachste Lösung

my_little_string.isalnum()

(Abschnitt 3.6.1 der Python-Bibliotheksreferenz)

11
Ber

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
5
Ber
 pat = re.compile ('[^\w-]')

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

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

1

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

0
William Keller

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.

0
Alston

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.

0
MB.