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

Posted

技术标签:

【中文标题】如何验证字符串是不是仅包含字母、数字、下划线和破折号?【英文标题】:How do I verify that a string only contains letters, numbers, underscores and dashes?如何验证字符串是否仅包含字母、数字、下划线和破折号? 【发布时间】:2010-09-10 12:49:04 【问题描述】:

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

【问题讨论】:

您说的是 ascii、特定区域设置还是 unicode 字母? 【参考方案1】:

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

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

【讨论】:

所有这些字符必须在一个类中,否则你会得到假阴性。此外,您忘记包含字符串开头和字符串结尾标记...像这样,只要存在一个有效字符,它就会始终匹配。 即使没有有效字符,这实际上也会匹配。零长度匹配。另外,它不在 python 中。【参考方案2】:

正则表达式只需很少的代码就可以解决问题:

import re

...

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

【讨论】:

您可以将其简化为:^[\w\d_-]*$ 此解决方案将匹配长度为零的字符串。使用 + 而不是 * 使其匹配 1 个或多个字符的字符串。 @Prestaul: \w 包括\d_,因此isvalid = re.match(r'[\w-]+$', astr)isinvalid = re.search(r'[^\w-]', astr)。可能存在 locale.setlocale 或 unicode 字符串需要额外考虑。 更正:isvalid = re.match(r'[\w-]*$', astr) -- 空字符串有效。 如何在该正则表达式中还允许使用句点/点 (.)?编辑,方法如下:^[a-zA-Z0-9-_\s\.]+$【参考方案3】:
 pat = re.compile ('[^\w-]')

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

【讨论】:

【参考方案4】:

您始终可以使用列表推导式并检查所有结果,这将比使用正则表达式占用更少的资源:@​​987654321@

【讨论】:

请在发布之前测试您的代码。根据您的错误答案运行的解决方案是: all(c in string.letters + string.digits + "_" for c in mystring) 这将比正则表达式消耗更多的资源。它对每个字符进行线性扫描(最好提前构建一个集合),而当生成器理解更轻量级时,您将不必要地构建一个列表。【参考方案5】:

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

【讨论】:

在您的第一个正则表达式测试中,“[a-zA-Z0-9_-]+$”不应该是“[a-zA-Z0-9_-]*$”。空字符串可能应该被认为是匹配的。 如果您使用 '[a-zA-Z]' 正则表达式,请使用 string.ascii_letters【参考方案6】:

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

from sets import Set

allowed_chars = Set('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-')

if Set(my_little_sting).issubset(allowed_chars):
    # your action
    print True

【讨论】:

【参考方案7】:

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

my_little_string.isalnum()

(Python 库参考的3.6.1 部分)

【讨论】:

不幸的是,该链接不再有效,但这是相关部分Python » 3.3.6 Documentation » The Python Standard Library » 4.7.1. String Methods。谢谢@Ber,这正是我所需要的。【参考方案8】:

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

使用 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 总是必须扫描整个字符串)。设置方法通常是最差的,仅针对空字符串情况击败正则表达式。

使用 all(x in allowed_set for x in s) 如果提早退出,效果会很好,但如果必须遍历每个字符,则可能会很糟糕。 isSubSet 和 set 的差异是可比的,并且无论数据如何,都始终与字符串的长度成正比。

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

【讨论】:

使用 string.ascii_letters 而不是 string.letters 如果您不使用 re.LOCALE 标志进行正则表达式(否则您可能会在 check_trans() 中得到误报结果。string.maketrans() 不适用于 unicode字符串。 对于 Python 3/Unicode/from __future__ import unicode_literals),使用 trans_table3 = dict((ord(char), '') for char in allowed_chars) 和 def check_trans(s): return not s.translate(trans_table3)。但总的来说,它的性能比 RE 版本差。【参考方案9】:

这里有一些基于 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 algorithm。

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

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

【讨论】:

【参考方案10】:

你可以向正则表达式寻求帮助,这里很棒:)

代码:

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  

希望这会有所帮助:)

【讨论】:

【参考方案11】:

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

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

\w:仅限[a-zA-Z0-9_]

所以你需要添加- char 来对齐连字符。

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

^:匹配字符串的开头。

$:匹配字符串的结尾。

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

&&&PATTERN&&PATTERN

【讨论】:

以上是关于如何验证字符串是不是仅包含字母、数字、下划线和破折号?的主要内容,如果未能解决你的问题,请参考以下文章

验证 UITextField 不包含字母

如何编写正则表达式以仅匹配数字、字母和破折号?

想要通过任何字母数字和下划线、破折号和句点

验证字符串以仅包含限定字符和中间的特定可选子字符串

如何验证密码是不是包含 X 大写字母和 Y 数字?

求PHP用户名注册验证正则表达式(6-16位字符,字母开头,只包含字母数字下划线)可支持中文