[Редактировать] Есть еще одно решение, еще не упомянутое, и оно, кажется, превосходит другие, приведенные до сих пор в большинстве случаев.
Используйте 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 и разница между множествами сравнимы и пропорциональны длине строки независимо от данных.
Существует аналогичная разница между методами регулярного выражения, соответствующими всем допустимым символам, и поиском недопустимых символов. Совпадение работает немного лучше при проверке длинной, но полностью допустимой строки, но хуже для недопустимых символов в конце строки.