Как проверить, что строка содержит только буквы, цифры, подчеркивания и тире? - PullRequest
78 голосов
/ 18 сентября 2008

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

Ответы [ 11 ]

112 голосов
/ 18 сентября 2008

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

import re

...

if re.match("^[A-Za-z0-9_-]*$", my_little_string):
    # do something here
22 голосов
/ 18 сентября 2008

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

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

Использование all (x в allow_set для x in s) работает хорошо, если оно выручает рано, но может быть плохо, если ему приходится перебирать каждый символ. isSubSet и разница между множествами сравнимы и пропорциональны длине строки независимо от данных.

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

14 голосов
/ 18 сентября 2008

Существует множество способов достижения этой цели, некоторые из которых более ясны, чем другие. Для каждого из моих примеров «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'. Посмотрите на этот пример, который удаляет из исходной строки все разрешенные символы, оставляя нас с набором, содержащим либо a) ничего, либо b) нарушающие символы из строки:

def check_set(mystring):
    return not set(mystring) - set(allowed)
10 голосов
/ 18 сентября 2008

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

my_little_string.isalnum()

(Раздел 3.6.1 Справочника по библиотеке Python)

4 голосов
/ 18 сентября 2008

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

from sets import Set

allowed_chars = Set('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-')

if Set(my_little_sting).issubset(allowed_chars):
    # your action
    print True
3 голосов
/ 18 сентября 2008
 pat = re.compile ('[^\w-]')

 def onlyallowed(s):
    return not pat.search (s)
1 голос
/ 22 января 2019

Регулярное выражение может быть очень гибким.

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 голосов
/ 14 ноября 2013

Ну, вы можете обратиться за помощью к регулярному выражению, великий здесь:)

код:

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  

Надеюсь, это поможет:)

0 голосов
/ 30 ноября 2012

Вот кое-что, основанное на «наивном подходе» Иерува (наивно быть его словами, а не моими!):

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

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

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

0 голосов
/ 18 сентября 2008

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...