it-swarm.cn

如何验证字符串是否仅包含字母,数字,下划线和短划线?

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

78
Ethan Post

正则表达式将使用非常少的代码来完成这个工作:

import re

...

if re.match("^[A-Za-z0-9_-]*$", my_little_string):
    # do something here
111
Thomas

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

使用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的差异是可比较的,并且无论数据如何,都始终与字符串的长度成比例。

匹配所有有效字符的正则表达式方法与搜索无效字符之间存在类似差异。在检查长而完全有效的字符串时匹配执行得更好,但对于字符串末尾附近的无效字符更糟糕。

22
Brian

有多种方法可以实现这一目标,有些方法比其他方法更清晰。对于我的每个例子,'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)
15
Jerub

如果它不是破折号和下划线,那么最简单的解决方案就是

my_little_string.isalnum()

(部分 3.6.1 Python库参考)

11
Ber

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

from sets import Set

allowed_chars = Set('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-')

if Set(my_little_sting).issubset(allowed_chars):
    # your action
    print True
5
Ber
 pat = re.compile ('[^\w-]')

 def onlyallowed(s):
    return not pat.search (s)
4
Javier

正则表达式可以非常灵活。

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

\w:只有[a-zA-Z0-9_]

因此,您需要添加- char以用于对齐连字符char。

+:匹配前一个char的一个或多个重复。我猜你不接受空白输入。但如果你这样做,请改为*

^:匹配字符串的开头。

$:匹配字符串的结尾。

您需要这两个特殊字符,因为您需要避免以下情况。像&这样不需要的字符可能出现在匹配的模式之间。

&&&PATTERN&&PATTERN

1
Alston

那么你可以请求正则表达式的帮助,这里的伟大:)

码:

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  

希望这可以帮助 :)

1
Sravan K Ghantasala

您总是可以使用列表推导并使用all检查结果,与使用正则表达式相比,它的资源密集程度要小一些:all([c in string.letters + string.digits + ["_", "-"] for c in mystring])

0
William Keller

这是基于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算法

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

_实际_ 在我的机器上,正则表达式运行速度快了几倍,并且就像这一样简单(可以说更简单)。所以这可能是最好的前进方式。

0
MB.