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


86

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


5
您是在谈论ascii,特定于语言环境的还是unicode字母?
jfs

Answers:


122

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

import re

...

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

25
您可以将其简化为:^ [\ w \ d _-] * $
Prestaul

13
此解决方案将匹配长度为零的字符串。使用+代替*使其匹配1个或多个字符的字符串。
杰鲁布

10
@Prestaul:\w包含\d_,因此为isvalid = re.match(r'[\w-]+$', astr)isinvalid = re.search(r'[^\w-]', astr)。可能存在locale.setlocaleunicode字符串或其他字符串。
jfs

1
更正:isvalid = re.match(r'[\w-]*$', astr)-空字符串有效。
jfs

您还如何在该正则表达式中允许使用句点/点(。)?编辑,方法如下:^ [a-zA-Z0-9 -_ \ s \。] + $
fredrik

24

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

使用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 inallow_set中的x for s中的x in)效果好,则可以尽早解决,但如果必须迭代每个字符,则效果会很差。isSubSet和set差异是可比较的,并且无论数据如何,其始终与字符串的长度成比例。

匹配所有有效字符的正则表达式方法与搜索无效字符之间存在相似的区别。当检查一个较长但完全有效的字符串时,匹配的性能要好一些,但在字符串末尾的无效字符时,匹配的性能会更差。


1
如果您不对正则表达式使用re.LOCALE标志,请使用string.ascii_letters代替string.letters(否则,您可能会在中得到假阳性结果check_trans()string.maketrans()不适用于unicode字符串。)
jfs

对于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版本差。
雨果

14

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

您也可以使用“设置”对象做一些棘手的事情。请看以下示例,该示例从原始字符串中删除了所有允许的字符,从而为我们提供了一个包含以下内容的集合:a)没有任何内容,或b)字符串中有问题的字符:

def check_set(mystring):
    return not set(mystring) - set(allowed)

在您的第一个正则表达式测试中,“ [a-zA-Z0-9 _-] + $”不应为“ [a-zA-Z0-9 _-] * $”。空字符串可能应该被认为是匹配的。
布赖恩

使用string.ascii_letters如果使用'[A-ZA-Z]'正则表达式。
jfs


4

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

from sets import Set

allowed_chars = Set('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-')

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

3
 pat = re.compile ('[^\w-]')

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

1

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

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

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

因此,您需要添加-char以证明连字符char。

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

^:匹配字符串的开头。

$:匹配字符串的结尾。

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

&&&PATTERN&&PATTERN


0

好吧,您可以向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  

希望这可以帮助 :)


-1

您总是可以使用列表推导并全部检查结果,这比使用正则表达式要少一些资源: all([c in string.letters + string.digits + ["_", "-"] for c in mystring])


请在发布之前测试您的代码。一个基于您运行失败的答案的解决方案是:all(c。string.letters + string.digits +“ _”代表mystring中的c)
Jerub

2
那将比正则表达式占用更多的资源。它会对每个字符进行线性扫描(最好是提前建立一个字符集),而当生成器理解会更轻量时,您就不需要构建列表。
Brian

-1

这是基于杰鲁布的“幼稚的方法”(幼稚的是他的话,不是我的话!):

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的话来说,这有点像Palemer Shlemiel算法

但是测试集中是否存在应该更有效,或者至少更少地依赖于允许的字符数。当然,这种方法在我的机器上要快一些。很明显,我认为在大多数情况下它的性能都足够好(在我的慢速机器上,我可以在不到一秒钟的时间内验证成千上万个短字符串)。我喜欢。

实际上,在我的机器上,正则表达式的运行速度快了好几倍,并且就这么简单(可以说更简单)。因此,这可能是最好的方法。


-4

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

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

1
所有这些字符必须在一个类中,否则您将得到假阴性。同样,您也忘记了包括字符串开头和字符串结尾标记...这样,只要存在一个有效字符,它将始终匹配。
托马斯

1
即使没有有效的字符,这实际上也会匹配。零长度匹配。另外,它不在python中。
杰鲁布
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.