Как проверить, что строка содержит только буквы, цифры, подчеркивания и тире?


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

10 73

10 ответов:

регулярное выражение будет делать трюк с очень мало кода:

import re

...

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

[Edit] есть еще одно решение, которое еще не упомянуто, и оно, похоже, превосходит другие, данные до сих пор в большинстве случаев.

использовать string.переведите, чтобы заменить все допустимые символы в строке, и посмотрите, остались ли у нас какие-либо недопустимые. Это довольно быстро, поскольку он использует базовую функцию C для выполнения работы,с очень небольшим байт-кодом python.

очевидно, что производительность-это еще не все - для самых читаемых решений, вероятно, лучше всего подход, когда не в критической производительности кодового пути, но просто чтобы увидеть, как решения складываются, вот сравнение производительности всех методов, предложенных до сих пор. check_trans-это тот, который использует строку.перевести метод.

тестовый код:

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 (предположительно потому, что регулярное выражение может выручить сразу, но translate всегда должен сканировать всю строку). Подходы к набору обычно хуже всего, избивая регулярные выражения только для пустого строкового случая.

использование all (x в allowed_set для x в s) хорошо работает, если он выпрыгивает рано, но может быть плохо, если он должен повторять каждый символ. isSubSet и set difference сопоставимы и последовательно пропорциональны длине строки независимо от данных.

есть похожие разница между методами регулярных выражений, сопоставляющими все допустимые символы и ищущими недопустимые символы. Сопоставление выполняется немного лучше при проверке длинной, но полностью допустимой строки, но хуже для недопустимых символов в конце строки.

есть множество способов достижения этой цели, некоторые из них яснее, чем другие. Для каждого из моих примеров "True" означает, что переданная строка действительна, "False" означает, что она содержит недопустимые символы.

прежде всего, есть наивный подход:

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

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

тогда есть использование регулярного выражения, вы можете сделать это с помощью re.спичка.)( Обратите внимание что '-' должен быть в конце [] в противном случае он будет использоваться в качестве разделителя. Также обратите внимание на $, что означает 'конец из струн". Другие ответы, отмеченные в этом вопросе, используют специальный класс символов "\w", я всегда предпочитаю использовать явный диапазон классов символов с использованием [], потому что его легче понять, не глядя на краткое справочное руководство, и проще в специальном случае.

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

другое решение отметило, что вы можете сделать обратное соответствие с регулярными выражениями, я включил это здесь и сейчас. Заметить что.^[ ..] инвертирует класс символов, потому что ^ is используется:

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)

Если бы не тире и подчеркивания, самым простым решением было бы

my_little_string.isalnum()

(раздел 3.6.1 ссылки на библиотеку Python)

в качестве альтернативы использованию regex вы можете сделать это в наборах:

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)

Ну вы можете попросить помощи regex, великий здесь:)

код:

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  

надеюсь, что это помогает :)

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

вот что-то на основе "наивного подхода Jerub у" (наивно-его слова, не мои!):

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 будет включать итерацию по каждому символу в строке, пока он не найдет совпадение или не достигнет конца. Что, по словам Джоэла Спольски, является чем-то вроде алгоритм художника Шлемиэля.

но тестирование на существование в наборе должно быть более эффективным или, по крайней мере, менее зависимым от количества разрешенных письмена. Конечно, такой подход немного быстрее на моей машине. Это ясно, и я думаю, что он работает достаточно хорошо для большинства случаев (на моей медленной машине я могу проверить десятки тысяч коротких строк за долю секунды). Мне это нравится.

на самом деле на моей машине регулярное выражение работает в несколько раз быстрее, а так же просто, как это (возможно проще). Так что, вероятно, это лучший путь вперед.

используйте регулярное выражение и посмотрите, соответствует ли оно!

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