如果我遍历字符串中的所有字符,我知道如何做到这一点,但我正在寻找一种更优雅的方法。
11 回答
正则表达式只需很少的代码就可以解决问题:
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 in allowed_set for x in s) 如果提早退出,效果会很好,但如果必须遍历每个字符,则可能会很糟糕。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)
作为使用正则表达式的替代方法,您可以在 Sets 中执行此操作:
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)
正则表达式可以非常灵活。
import re;
re.fullmatch("^[\w-]+$", target_string) # fullmatch looks also workable for python 3.4
\w
: 仅有的[a-zA-Z0-9_]
因此,您需要添加-
char 来对齐连字符 char。
+
: 匹配前一个字符的一个或多个重复。我猜你不接受空白输入。但是,如果您这样做,请更改为*
.
^
: 匹配字符串的开头。
$
: 匹配字符串的结尾。
您需要这两个特殊字符,因为您需要避免以下情况。像这里这样不需要的字符&
可能会出现在匹配的模式之间。
&&&PATTERN&&PATTERN
好吧,您可以寻求正则表达式的帮助,这里很棒:)
代码:
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
将涉及迭代字符串中的每个字符,直到找到匹配项或到达末尾。引用 Joel Spolsky 的话,这有点像Shlemiel the Painter 算法。
但是测试集合中的存在应该更有效,或者至少更少依赖于允许的字符数。当然,这种方法在我的机器上要快一点。很明显,我认为它在大多数情况下表现得足够好(在我的慢机器上,我可以在几分之一秒内验证数以万计的短字符串)。我喜欢。
实际上,在我的机器上,正则表达式的运行速度要快几倍,而且就像这样简单(可以说更简单)。所以这可能是最好的前进方式。
使用正则表达式,看看它是否匹配!
([a-z][A-Z][0-9]\_\-)*