代码之家  ›  专栏  ›  技术社区  ›  Ethan Post

如何验证字符串仅包含字母、数字、下划线和破折号?

  •  76
  • Ethan Post  · 技术社区  · 17 年前

    如果我遍历字符串中的所有字符,我知道如何做到这一点,但我正在寻找一种更优雅的方法。

    11 回复  |  直到 9 年前
        1
  •  132
  •   Thomas    17 年前

    正则表达式只需很少的代码即可完成此任务:

    import re
    
    ...
    
    if re.match("^[A-Za-z0-9_-]*$", my_little_string):
        # do something here
    
        2
  •  25
  •   Peter S. Housel    17 年前

    [编辑]还有另一个解决方案尚未提及,在大多数情况下,它的性能似乎优于目前给出的其他解决方案。

    使用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
    

    在大多数情况下,translate方法似乎是最好的,对于长有效字符串,效果非常显著,但在test_long_invalid中被正则表达式击败(可能是因为正则表达式可以立即退出,但translate始终必须扫描整个字符串)。集合方法通常是最差的,仅在空字符串情况下优于正则表达式。

    在匹配所有有效字符和搜索无效字符的正则表达式方法之间有类似的区别。在检查长但完全有效的字符串时,匹配性能稍好一些,但在接近字符串末尾的无效字符时,匹配性能更差。

        3
  •  15
  •   Jerub    17 年前

    实现这一目标有多种方式,有些方式比其他方式更为明确。对于我的每个示例,“True”表示传递的字符串有效,“False”表示它包含无效字符。

    首先,有一种天真的方法:

    import string
    allowed = string.letters + string.digits + '_' + '-'
    
    def check_naive(mystring):
        return all(c in allowed for c in mystring)
    

    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)nothing,或b)字符串中有问题的字符:

    def check_set(mystring):
        return not set(mystring) - set(allowed)
    
        4
  •  13
  •   Ber    10 年前

    如果没有破折号和下划线,最简单的解决方案是

    my_little_string.isalnum()
    

    (节) 3.6.1

        5
  •  4
  •   Ber    17 年前

    作为使用正则表达式的替代方法,您可以在集合中执行:

    from sets import Set
    
    allowed_chars = Set('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-')
    
    if Set(my_little_sting).issubset(allowed_chars):
        # your action
        print True
    
        6
  •  3
  •   Javier    17 年前
     pat = re.compile ('[^\w-]')
    
     def onlyallowed(s):
        return not pat.search (s)
    
        7
  •  2
  •   Alston    7 年前

    import re;
    re.fullmatch("^[\w-]+$", target_string) # fullmatch looks also workable for python 3.4
    

    \w [a-zA-Z0-9_]

    所以你需要加上 -

    + :匹配前面字符的一个或多个重复。我猜你不接受空白输入。但如果你这样做了,就换成 * .

    ^ :匹配字符串的开头。

    $ :匹配字符串的结尾。

    & 这里可能会出现在匹配的图案之间。

    &&&PATTERN&&PATTERN

        8
  •  0
  •   Sravan K Ghantasala    12 年前

    你可以求助于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  
    

    希望这有帮助:)

        9
  •  -1
  •   William Keller    17 年前

    您可以始终使用列表理解并使用all检查结果,这将比使用正则表达式少一点资源密集度: all([c in string.letters + string.digits + ["_", "-"] for c in mystring])

        10
  •  -1
  •   MB.    13 年前

    以下是基于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 将涉及迭代字符串中的每个字符,直到找到匹配项或到达末尾。引用乔尔·斯波尔斯基的话,这是一个 Shlemiel the Painter algorithm .

    但是测试集合中是否存在应该更有效,或者至少不太依赖于允许的字符数。当然,这种方法在我的机器上要快一点。这很清楚,而且我认为它在大多数情况下都表现得足够好(在我的慢速机器上,我可以在几分之一秒内验证成千上万个短字符串)。我喜欢。

    事实上 在我的机器上,regexp的计算速度快了好几倍,而且也和这个一样简单(可以说更简单)。因此,这可能是最好的前进方式。

        11
  •  -4
  •   easeout    17 年前

    使用正则表达式,看看它是否匹配!

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