如果我遍历字符串中的所有字符,我知道如何做到这一点,但我正在寻找一个更优雅的方法.
正则表达式将使用非常少的代码来完成这个工作:
import re ... if re.match("^[A-Za-z0-9_-]*$", my_little_string): # do something here
[编辑]还有另一个尚未提及的解决方案,在大多数情况下,它似乎优于迄今为止给出的其他解决方案.
使用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中的x为allowed_set),如果它提前退出,则表现良好,但如果必须遍历每个字符,则可能会很糟糕.isSubSet和set的差异是可比较的,并且无论数据如何,都始终与字符串的长度成比例.
匹配所有有效字符的正则表达式方法与搜索无效字符之间存在类似差异.在检查长而完全有效的字符串时匹配执行得更好,但对于字符串末尾附近的无效字符更糟糕.
有多种方法可以实现这一目标,有些方法比其他方法更清晰.对于我的每个例子,'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)
如果它不是破折号和下划线,那么最简单的解决方案就是
my_little_string.isalnum()
(Python库参考的第3.6.1节)