Jak mogę sprawdzić, czy ciąg zawiera tylko litery, cyfry, znaki podkreślenia i myślniki?

głosy
70

Wiem, jak to zrobić, jeśli iterację wszystkich znaków w ciągu, ale szukam bardziej elegancki sposób.

Utwórz 18/09/2008 o 05:04
źródło użytkownik
W innych językach...                            


11 odpowiedzi

głosy
101

Wyrażenie regularne rade bardzo mało kodu:

import re

...

if re.match("^[A-Za-z0-9_-]*$", my_little_string):
    # do something here
Odpowiedział 18/09/2008 o 05:08
źródło użytkownik

głosy
21

[Edit] Nie ma innego rozwiązania jeszcze nie wspomniano, a wydaje się prześcignąć innym podane do tej pory w większości przypadków.

Użyj string.translate wymienić wszystkich prawidłowych znaków w łańcuchu, i sprawdzić, czy mamy jakiekolwiek nieprawidłowe te pozostały. Jest to dość szybko, gdyż korzysta z leżącą u podstaw funkcji C do pracy, z bardzo małym Pythona kodu bajtowego zaangażowanych.

Oczywiście wydajność to nie wszystko - będzie dla najbardziej czytelnych rozwiązań jest prawdopodobnie najlepszym rozwiązaniem, gdy nie jest w krytycznym codepath wydajności, ale po prostu zobaczyć, w jaki sposób rozwiązania stos, oto porównanie wydajności wszystkich metod proponowanych do tej pory. check_trans jedną stosując metodę string.translate.

Kod testu:

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

Wyniki na moim systemie to:

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

Podejście tłumaczyć wydaje się najlepiej w większości przypadków tak dramatycznie długie ważnych ciągów, ale zostaje pobity przez regexes w test_long_invalid (prawdopodobnie dlatego, że regex może wyskoczyć natychmiast, ale zawsze musi tłumaczyć skanować cały ciąg). Ustawione podejścia są zwykle najgorsze, pokonując regexes tylko dla pustej przypadku strun.

Korzystanie wszystkie (X w allowed_set X w S) działa dobrze, jeśli poręczeń wcześnie, ale może być źle, jeśli to ma wykonać iterację każdej postaci. isSubSet i ustawić różnicę są porównywalne, i są stale proporcjonalna do długości łańcucha niezależnie od danych.

Jest podobna różnica między regex metodami pasującymi wszystkich prawidłowych znaków i poszukujących nieprawidłowych znaków. Matching wykonuje trochę lepiej po sprawdzeniu przez długi, ale w pełni prawidłowy ciąg, ale gorzej dla niepoprawnych znaków blisko końca łańcucha.

Odpowiedział 18/09/2008 o 13:19
źródło użytkownik

głosy
13

Istnieje wiele sposobów na osiągnięcie tego celu, niektóre są jaśniejsze niż inne. Dla każdego z moich przykładach, „Prawda” oznacza, że ​​ciąg przekazywane jest ważny, „false” oznacza to, że zawiera nieprawidłowe znaki.

Przede wszystkim, nie jest naiwny podejście:

import string
allowed = string.letters + string.digits + '_' + '-'

def check_naive(mystring):
    return all(c in allowed for c in mystring)

Potem jest użycie wyrażenia regularnego, można to zrobić z re.match (). Należy zauważyć, że „-” musi być na koniec [], w przeciwnym razie będzie się stosować jako „zakres” ogranicznik. Należy również pamiętać o $, co oznacza „koniec sznurka”. Inne odpowiedzi w tej kwestii zauważyć użyć specjalnego klasę postaci, „\ W”, zawsze wolą używać wyraźny zakres klasa znaków używając [], ponieważ łatwiej jest zrozumieć bez konieczności patrzenia w górę krótki przewodnik, i łatwiejsze do specjalistycznego walizka.

import re
CHECK_RE = re.compile('[a-zA-Z0-9_-]+$')
def check_re(mystring):
    return CHECK_RE.match(mystring)

Innym rozwiązaniem zauważyć, że można zrobić odwrotną mecz z wyrażeń regularnych, podaję, że teraz tutaj. Zauważ, że [^ ...] odwraca klasę postaci, ponieważ ^ stosuje się:

CHECK_INV_RE = re.compile('[^a-zA-Z0-9_-]')
def check_inv_re(mystring):
   return not CHECK_INV_RE.search(mystring)

Można również zrobić coś trudne z „zestawu” obiektu. Spojrzeć na ten przykład, który usuwa z oryginalnego ciąg wszystkie znaki, które są dozwolone, pozostawiając nas z zestawem zawierającym albo a) nic, albo b) znaki naruszającym ciągu:

def check_set(mystring):
    return not set(mystring) - set(allowed)
Odpowiedział 18/09/2008 o 05:18
źródło użytkownik

głosy
10

Gdyby nie było dla kresek i podkreśleń, najprostszym rozwiązaniem byłoby

my_little_string.isalnum()

(Sekcja 3.6.1 na Python Library Reference)

Odpowiedział 18/09/2008 o 11:49
źródło użytkownik

głosy
5

Jako alternatywę dla korzystania regex można zrobić w zestawach:

from sets import Set

allowed_chars = Set('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-')

if Set(my_little_sting).issubset(allowed_chars):
    # your action
    print True
Odpowiedział 18/09/2008 o 11:47
źródło użytkownik

głosy
3
 pat = re.compile ('[^\w-]')

 def onlyallowed(s):
    return not pat.search (s)
Odpowiedział 18/09/2008 o 05:12
źródło użytkownik

głosy
1

Cóż można poprosić o pomoc regex, wielki tutaj :)

kod:

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'

Wydajność:

yes  

Mam nadzieję że to pomoże :)

Odpowiedział 14/11/2013 o 07:04
źródło użytkownik

głosy
0

Wyrażenie regularne może być bardzo elastyczny.

import re;
re.fullmatch("^[\w-]+$", target_string) # fullmatch starts from python 3.4 `match` looks also workable here

\w: Tylko [a-zA-Z0-9_]

Więc trzeba dodać -char.

+: Dopasować jedno lub więcej powtórzeń poprzedniego char. Chyba nie akceptują pusty wkład. Ale jeśli nie, zmieni się *.

^: Dopasowuje początek napisu.

$: Dopasowuje koniec łańcucha.

Trzeba te dwa znaki specjalne ponieważ trzeba uniknąć następujące sprawy:

&&&PATTERN&&PATTERN

Wzór nie chcesz chyba może usiąść między wzorami chcesz.

W tym przypadku: &&&nie ma to można się spodziewać, ale ciąg legaljest dopuszczalne. Jeśli nie dodawać ^i $do wyrażenia regularnego, to wzór będzie pasował do złego wzorca.

Odpowiedział 22/01/2019 o 11:05
źródło użytkownik

głosy
0

Oto coś na podstawie „naiwnego podejścia” Jerub za (naiwny będąc jego słowa, nie moje!):

import string
ALLOWED = frozenset(string.ascii_letters + string.digits + '_' + '-')

def check(mystring):
    return all(c in ALLOWED for c in mystring)

Jeśli ALLOWEDbył ciąg to myślę, że c in ALLOWEDwiązałoby Iteracja nad każdym znaku w ciągu aż znalazł mecz lub dobiegł końca. Który, cytując Joel Spolsky, jest czymś w rodzaju algorytmu malarza Shlemiel .

Ale badania istnienia w zestawie powinien być bardziej wydajny, a przynajmniej mniej zależna od liczby dozwolonych znaków. Z pewnością takie podejście jest trochę szybciej na moim komputerze. To jasne i myślę, że wykonuje wystarczająco dobrze w większości przypadków (na moim wolnym komputerze mogę potwierdzić dziesiątki tysięcy krótkich ciągów-owski w ułamku sekundy). Lubię to.

FAKTYCZNIE na moim komputerze wyrażeniem regularnym działa obecnie kilka razy szybciej, a jest tak samo proste, jak ten (zapewne prostsze). Tak że prawdopodobnie jest najlepszym rozwiązaniem.

Odpowiedział 30/11/2012 o 17:50
źródło użytkownik

głosy
0

Zawsze możesz użyć wyrażeń listowych i sprawdzić wyniki z wszystkich, byłoby trochę mniej intensywne niż przy użyciu zasobów regex: all([c in string.letters + string.digits + ["_", "-"] for c in mystring])

Odpowiedział 18/09/2008 o 05:12
źródło użytkownik

głosy
-2

używać regex i zobaczyć czy pasuje!

([a-z][A-Z][0-9]\_\-)*
Odpowiedział 18/09/2008 o 05:06
źródło użytkownik

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more