Як мені перевірити, що рядок містить лише літери, цифри, підкреслення та тире?


86

Я знаю, як це зробити, якщо переглядаю всі символи рядка, але шукаю більш елегантний метод.


5
Ви говорите про ascii, регіональні літери чи літери Unicode?
jfs

Відповіді:


122

Регулярний вираз зробить фокус з дуже невеликим кодом:

import re

...

if re.match("^[A-Za-z0-9_-]*$", my_little_string):
    # do something here

25
Ви можете спростити це до: ^ [\ w \ d _-] * $
Prestaul

13
Це рішення буде відповідати рядкам нульової довжини. використовуйте + замість *, щоб збігатись із рядками з 1 або більше символів.
Єруб

10
@Prestaul: \wвключає \dі _, отже, isvalid = re.match(r'[\w-]+$', astr)або isinvalid = re.search(r'[^\w-]', astr). Можлива наявність locale.setlocaleрядків або Unicode вимагає додаткового розгляду.
jfs

1
Виправлення: isvalid = re.match(r'[\w-]*$', astr)- порожні рядки дійсні.
jfs

Як ви також можете дозволити крапку / крапку (.) У цьому регулярному виразі? Редагувати, ось як: ^ [a-zA-Z0-9 -_ \ s \.] + $
fredrik

24

[Редагувати] Є ще одне рішення, про яке ще не згадувалося, і, схоже, воно в більшості випадків перевершує інші, наведені досі.

Використовуйте string.translate, щоб замінити всі дійсні символи в рядку, і подивіться, чи залишились у нас недійсні. Це досить швидко, оскільки для виконання роботи використовується основна функція C, задіяна дуже мала кількість байт-коду python.

Очевидно, що продуктивність - це не все - вибір найбільш читаних рішень - це, мабуть, найкращий підхід, коли немає критичного шляху продуктивності, але просто для того, щоб побачити, як рішення укладаються, ось порівняння продуктивності всіх запропонованих до цього часу методів. check_trans - це той, що використовує метод string.translate.

Тестовий код:

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

Результати в моїй системі:

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

Підхід до перекладу здається найкращим у більшості випадків, різко, з довгими дійсними рядками, але вибивається регулярними виразами в test_long_invalid (імовірно, тому що регулярний вираз може негайно виручити, але перекладач завжди повинен сканувати весь рядок). Набори підходів, як правило, гірші, перемагаючи регулярні вирази лише для порожнього рядка.

Використання всіх (x у дозволеному_наборі для x в s) працює добре, якщо воно виручає раніше, але може бути поганим, якщо йому доведеться перебирати кожен символ. isSubSet та різниця в наборі порівнянні та незмінно пропорційні довжині рядка незалежно від даних.

Існує подібна різниця між методами регулярних виразів, що відповідають всім дійсним символам, і пошуком недійсних символів. Збіги виконуються трохи краще при перевірці довгого, але повністю дійсного рядка, але гірше для недійсних символів біля кінця рядка.


1
Використовуйте string.ascii_lettersзамість цього, string.lettersякщо ви не використовуєте прапор re.LOCALE для регулярних виразів (інакше ви можете отримати помилково позитивні результати в check_trans(). string.maketrans()Для рядків Unicode не буде працювати.
jfs,

Для Python 3 / Unicode / from __future__ import unicode_literals) використовуйте trans_table3 = dict((ord(char), '') for char in allowed_chars)та def check_trans(s): return not s.translate(trans_table3). Але загалом він працює гірше, ніж версії RE.
Гюго

14

Існує безліч способів досягнення цієї мети, деякі з них зрозуміліші за інші. Для кожного з моїх прикладів "True" означає, що переданий рядок є дійсним, "False" означає, що він містить недійсні символи.

Перш за все, існує наївний підхід:

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

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

Потім використовується регулярний вираз, ви можете зробити це за допомогою re.match (). Зверніть увагу, що "-" має бути в кінці [], інакше воно буде використано як роздільник "діапазону". Також зверніть увагу на $, що означає "кінець рядка". Інші відповіді, зазначені у цьому питанні, використовують спеціальний клас символів, '\ w', я завжди волію використовувати явний діапазон класів символів, використовуючи [], тому що це легше зрозуміти, не шукаючи короткого довідкового посібника, і легше спеціально справа.

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

Ще одне рішення зазначило, що ви можете зробити зворотний збіг із регулярними виразами, я включив це тут зараз. Зверніть увагу, що [^ ...] інвертує клас символів, оскільки використовується ^:

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

Ви також можете зробити щось хитре з об'єктом 'set'. Погляньте на цей приклад, який видаляє з вихідного рядка всі дозволені символи, залишаючи нам набір, що містить або а) нічого, або б) символи, що порушують з рядка:

def check_set(mystring):
    return not set(mystring) - set(allowed)

У вашому першому тесті регулярних виразів "[a-zA-Z0-9 _-] + $" не повинно бути "[a-zA-Z0-9 _-] * $". Порожній рядок, мабуть, слід вважати відповідним.
Брайан

Використовуйте, string.ascii_lettersякщо ви використовуєте регулярні вирази '[a-zA-Z]'.
jfs

12

Якби не тире та підкреслення, найпростішим рішенням було б

my_little_string.isalnum()

(Розділ 3.6.1 Посилання на бібліотеку Python)


На жаль, посилання більше не працює, але ось відповідний розділ Python »3.3.6 Документація» Стандартна бібліотека Python »4.7.1. Струнні методи . Дякую @Ber, це саме те, що мені потрібно.
Танос

4

Як альтернативу використанню регулярних виразів ви можете це зробити в наборах:

from sets import Set

allowed_chars = Set('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-')

if Set(my_little_sting).issubset(allowed_chars):
    # your action
    print True


1

Регулярний вираз може бути дуже гнучким.

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

\w: Тільки [a-zA-Z0-9_]

Отже, вам потрібно додати -char для виправдання дефіса char.

+: Зіставте одне або кілька повторень попереднього символу. Я думаю, ви не приймаєте пусте введення. Але якщо ви це зробите, перейдіть на *.

^: Відповідає початку рядка.

$: Відповідає кінці рядка.

Вам потрібні ці два спеціальні символи, оскільки вам потрібно уникати наступного випадку. Небажані символи, як &тут, можуть з’являтися між відповідним візерунком.

&&&PATTERN&&PATTERN


0

Ну, ви можете попросити допомоги регулярного виразу, чудового тут :)

код:

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'

Вихід:

yes  

Сподіваюся, це допомагає :)


-1

Ви завжди можете використовувати розуміння списку та перевірити результати з усіма, це було б трохи менш ресурсомістким, ніж використання регулярного виразу: all([c in string.letters + string.digits + ["_", "-"] for c in mystring])


Перевірте свій код перед публікацією. Рішення на основі вашої розбитої відповіді, яке працює: all (c у string.letters + string.digits + "_" для c у mystring)
Єруб

2
Це буде набагато більш ресурсомістким, ніж регулярний вираз. Він виконує лінійне сканування для кожного персонажа (краще побудувати набір заздалегідь), і ви без потреби формуєте список, коли розуміння генератора буде більш легким.
Брайан

-1

Ось щось, засноване на «наївному підході» Єруба (наївним є його слова, а не мої!):

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

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

Якби це ALLOWEDбув рядок, тоді, я думаю, c in ALLOWEDце передбачало б перебір кожного символу в рядку, поки він не знайшов збіг або не досяг кінця. Що, цитуючи Джоеля Спольського, є чимось на зразок алгоритму Шлеміеля Пейнтера .

Але тестування на наявність у наборі повинно бути більш ефективним або, принаймні, менш залежним від кількості дозволених символів. Звичайно, такий підхід трохи швидший на моїй машині. Це зрозуміло, і я думаю, що він працює достатньо добре для більшості випадків (на моїй повільній машині я можу перевірити десятки тисяч короткочасних рядків за частку секунди). Мені це подобається.

ВЛАСНО на моїй машині регулярний вираз працює в кілька разів швидше і є таким же простим, як це (можливо, простішим). Тож це, мабуть, найкращий шлях вперед.


-4

використовуйте регулярний вираз і подивіться, чи збігається він!

([a-z][A-Z][0-9]\_\-)*

1
Всі ці символи повинні бути в одному класі, інакше ви отримаєте помилкові негативи. Також ви забули включити маркери початку та кінця рядка ... ось так, він завжди збігатиметься, поки присутній один дійсний символ.
Томас

1
Це насправді буде відповідати, навіть якщо немає дійсних символів. Збіг нульової довжини. Крім того, це не в python.
Єруб
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.