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

时间:2021-07-27 20:16:46

I know how to do this if I iterate through all of the characters in the string but I am looking for a more elegant method.

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

10 个解决方案

#1


101  

A regular expression will do the trick with very little code:

一个正则表达式可以用非常小的代码来完成这个技巧:

import re

...

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

#2


21  

[Edit] There's another solution not mentioned yet, and it seems to outperform the others given so far in most cases.

还有一种解决方案尚未被提及,而且在大多数情况下,它的表现似乎优于其他的解决方案。

Use string.translate to replace all valid characters in the string, and see if we have any invalid ones left over. This is pretty fast as it uses the underlying C function to do the work, with very little python bytecode involved.

使用字符串。转换以替换字符串中的所有有效字符,并查看是否还有任何无效字符。这非常快,因为它使用底层C函数来完成这项工作,只涉及很少的python字节码。

Obviously performance isn't everything - going for the most readable solutions is probably the best approach when not in a performance critical codepath, but just to see how the solutions stack up, here's a performance comparison of all the methods proposed so far. check_trans is the one using the string.translate method.

显然,性能并不是万能的——在性能关键的代码页中寻找可读性最强的解决方案可能是最好的方法,但是为了看看解决方案是如何堆积起来的,下面是迄今为止提出的所有方法的性能比较。check_trans是使用字符串的那个。翻译方法。

Test code:

测试代码:

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()

The results on my system are:

我系统上的结果是:

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

The translate approach seems best in most cases, dramatically so with long valid strings, but is beaten out by regexes in test_long_invalid (Presumably because the regex can bail out immediately, but translate always has to scan the whole string). The set approaches are usually worst, beating regexes only for the empty string case.

翻译方法在大多数情况下似乎是最好的,特别是对于长有效字符串,但是在test_long_invalid中被regex击败(可能是因为regex可以立即退出,但是翻译总是要扫描整个字符串)。集合方法通常是最糟糕的,只在空字符串情况下打败regexes。

Using all(x in allowed_set for x in s) performs well if it bails out early, but can be bad if it has to iterate through every character. isSubSet and set difference are comparable, and are consistently proportional to the length of the string regardless of the data.

使用all(在allowed_set中为x在s中)如果它提前发出,性能会很好,但如果它必须遍历每个字符,性能就会很差。is子集和set差是可比较的,并且始终与字符串的长度成正比,无论数据如何。

There's a similar difference between the regex methods matching all valid characters and searching for invalid characters. Matching performs a little better when checking for a long, but fully valid string, but worse for invalid characters near the end of the string.

regex方法匹配所有有效字符和搜索无效字符之间存在类似的差异。在检查长且完全有效的字符串时,匹配的性能稍微好一些,但对于字符串末尾的无效字符更糟糕。

#3


13  

There are a variety of ways of achieving this goal, some are clearer than others. For each of my examples, 'True' means that the string passed is valid, 'False' means it contains invalid characters.

实现这一目标的方法有很多种,有些比另一些更清晰。对于我的每个示例,'True'表示传递的字符串是有效的,'False'表示它包含无效字符。

First of all, there's the naive approach:

首先,有一种朴素的方法:

import string
allowed = string.letters + string.digits + '_' + '-'

def check_naive(mystring):
    return all(c in allowed for c in mystring)

Then there's use of a regular expression, you can do this with re.match(). Note that '-' has to be at the end of the [] otherwise it will be used as a 'range' delimiter. Also note the $ which means 'end of string'. Other answers noted in this question use a special character class, '\w', I always prefer using an explicit character class range using [] because it is easier to understand without having to look up a quick reference guide, and easier to special-case.

然后使用正则表达式,可以使用re.match()实现这一点。注意,'-'必须在[]的末尾,否则将用作'range'分隔符。还要注意$的意思是“字符串的结束”。这个问题中提到的其他答案使用一个特殊的字符类'\w',我总是喜欢使用一个显式的字符类范围使用[],因为它更容易理解,而不需要查找快速参考指南,更容易用于特殊情况。

import re
CHECK_RE = re.compile('[a-zA-Z0-9_-]+$')
def check_re(mystring):
    return CHECK_RE.match(mystring)

Another solution noted that you can do an inverse match with regular expressions, I've included that here now. Note that [^...] inverts the character class because the ^ is used:

另一个解注意到你可以用正则表达式做逆匹配,我现在已经包含在这里了。注意,[^……]倒置使用字符类,因为^:

CHECK_INV_RE = re.compile('[^a-zA-Z0-9_-]')
def check_inv_re(mystring):
   return not CHECK_INV_RE.search(mystring)

You can also do something tricky with the 'set' object. Have a look at this example, which removes from the original string all the characters that are allowed, leaving us with a set containing either a) nothing, or b) the offending characters from the string:

您还可以对“set”对象执行一些棘手的操作。请看这个示例,它从原始字符串中删除所有允许的字符,留给我们的集合要么是a)什么都不包含,要么是b)字符串中不允许的字符:

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

#4


10  

If it were not for the dashes and underscores, the easiest solution would be

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

my_little_string.isalnum()

(Section 3.6.1 of the Python Library Reference)

(Python库引用的第3.6.1节)

#5


5  

As an alternative to using regex you could do it in Sets:

作为使用regex的替代方法,您可以在集合中进行:

from sets import Set

allowed_chars = Set('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-')

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

#6


3  

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

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

#7


1  

Well you can ask the help of regex, the great in here :)

你可以请求regex的帮助,这里的great

code:

代码:

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'

Output:

输出:

yes  

Hope this helps :)

希望这有助于:)

#8


0  

You could always use a list comprehension and check the results with all, it would be a little less resource intensive than using a regex: all([c in string.letters + string.digits + ["_", "-"] for c in mystring])

您可以使用列表理解并检查所有结果,与使用regex: all(字符串中的[c])相比,它的资源消耗要少一些。字母+字符串。在mystring中c的数字+ ["_","-"])

#9


0  

Here's something based on Jerub's "naive approach" (naive being his words, not mine!):

这是基于Jerub的“天真的方法”(天真是他的话,不是我的!)

import string
ALLOWED = frozenset(string.ascii_letters + string.digits + '_' + '-')

def check(mystring):
    return all(c in ALLOWED for c in mystring)

If ALLOWED was a string then I think c in ALLOWED would involve iterating over each character in the string until it found a match or reached the end. Which, to quote Joel Spolsky, is something of a Shlemiel the Painter algorithm.

如果允许是一个字符串,那么我认为允许的c将涉及到遍历字符串中的每个字符,直到找到匹配或到达末尾。用Joel Spolsky的话来说,这有点像画家算法。

But testing for existence in a set should be more efficient, or at least less dependent on the number of allowed characters. Certainly this approach is a little bit faster on my machine. It's clear and I think it performs plenty well enough for most cases (on my slow machine I can validate tens of thousands of short-ish strings in a fraction of a second). I like it.

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

ACTUALLY on my machine a regexp works out several times faster, and is just as simple as this (arguably simpler). So that probably is the best way forward.

实际上,在我的机器上,regexp运算速度要快几倍,并且与此一样简单(可以说更简单)。所以这可能是最好的方法。

#10


-2  

use a regex and see if it matches!

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

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

#1


101  

A regular expression will do the trick with very little code:

一个正则表达式可以用非常小的代码来完成这个技巧:

import re

...

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

#2


21  

[Edit] There's another solution not mentioned yet, and it seems to outperform the others given so far in most cases.

还有一种解决方案尚未被提及,而且在大多数情况下,它的表现似乎优于其他的解决方案。

Use string.translate to replace all valid characters in the string, and see if we have any invalid ones left over. This is pretty fast as it uses the underlying C function to do the work, with very little python bytecode involved.

使用字符串。转换以替换字符串中的所有有效字符,并查看是否还有任何无效字符。这非常快,因为它使用底层C函数来完成这项工作,只涉及很少的python字节码。

Obviously performance isn't everything - going for the most readable solutions is probably the best approach when not in a performance critical codepath, but just to see how the solutions stack up, here's a performance comparison of all the methods proposed so far. check_trans is the one using the string.translate method.

显然,性能并不是万能的——在性能关键的代码页中寻找可读性最强的解决方案可能是最好的方法,但是为了看看解决方案是如何堆积起来的,下面是迄今为止提出的所有方法的性能比较。check_trans是使用字符串的那个。翻译方法。

Test code:

测试代码:

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()

The results on my system are:

我系统上的结果是:

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

The translate approach seems best in most cases, dramatically so with long valid strings, but is beaten out by regexes in test_long_invalid (Presumably because the regex can bail out immediately, but translate always has to scan the whole string). The set approaches are usually worst, beating regexes only for the empty string case.

翻译方法在大多数情况下似乎是最好的,特别是对于长有效字符串,但是在test_long_invalid中被regex击败(可能是因为regex可以立即退出,但是翻译总是要扫描整个字符串)。集合方法通常是最糟糕的,只在空字符串情况下打败regexes。

Using all(x in allowed_set for x in s) performs well if it bails out early, but can be bad if it has to iterate through every character. isSubSet and set difference are comparable, and are consistently proportional to the length of the string regardless of the data.

使用all(在allowed_set中为x在s中)如果它提前发出,性能会很好,但如果它必须遍历每个字符,性能就会很差。is子集和set差是可比较的,并且始终与字符串的长度成正比,无论数据如何。

There's a similar difference between the regex methods matching all valid characters and searching for invalid characters. Matching performs a little better when checking for a long, but fully valid string, but worse for invalid characters near the end of the string.

regex方法匹配所有有效字符和搜索无效字符之间存在类似的差异。在检查长且完全有效的字符串时,匹配的性能稍微好一些,但对于字符串末尾的无效字符更糟糕。

#3


13  

There are a variety of ways of achieving this goal, some are clearer than others. For each of my examples, 'True' means that the string passed is valid, 'False' means it contains invalid characters.

实现这一目标的方法有很多种,有些比另一些更清晰。对于我的每个示例,'True'表示传递的字符串是有效的,'False'表示它包含无效字符。

First of all, there's the naive approach:

首先,有一种朴素的方法:

import string
allowed = string.letters + string.digits + '_' + '-'

def check_naive(mystring):
    return all(c in allowed for c in mystring)

Then there's use of a regular expression, you can do this with re.match(). Note that '-' has to be at the end of the [] otherwise it will be used as a 'range' delimiter. Also note the $ which means 'end of string'. Other answers noted in this question use a special character class, '\w', I always prefer using an explicit character class range using [] because it is easier to understand without having to look up a quick reference guide, and easier to special-case.

然后使用正则表达式,可以使用re.match()实现这一点。注意,'-'必须在[]的末尾,否则将用作'range'分隔符。还要注意$的意思是“字符串的结束”。这个问题中提到的其他答案使用一个特殊的字符类'\w',我总是喜欢使用一个显式的字符类范围使用[],因为它更容易理解,而不需要查找快速参考指南,更容易用于特殊情况。

import re
CHECK_RE = re.compile('[a-zA-Z0-9_-]+$')
def check_re(mystring):
    return CHECK_RE.match(mystring)

Another solution noted that you can do an inverse match with regular expressions, I've included that here now. Note that [^...] inverts the character class because the ^ is used:

另一个解注意到你可以用正则表达式做逆匹配,我现在已经包含在这里了。注意,[^……]倒置使用字符类,因为^:

CHECK_INV_RE = re.compile('[^a-zA-Z0-9_-]')
def check_inv_re(mystring):
   return not CHECK_INV_RE.search(mystring)

You can also do something tricky with the 'set' object. Have a look at this example, which removes from the original string all the characters that are allowed, leaving us with a set containing either a) nothing, or b) the offending characters from the string:

您还可以对“set”对象执行一些棘手的操作。请看这个示例,它从原始字符串中删除所有允许的字符,留给我们的集合要么是a)什么都不包含,要么是b)字符串中不允许的字符:

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

#4


10  

If it were not for the dashes and underscores, the easiest solution would be

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

my_little_string.isalnum()

(Section 3.6.1 of the Python Library Reference)

(Python库引用的第3.6.1节)

#5


5  

As an alternative to using regex you could do it in Sets:

作为使用regex的替代方法,您可以在集合中进行:

from sets import Set

allowed_chars = Set('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-')

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

#6


3  

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

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

#7


1  

Well you can ask the help of regex, the great in here :)

你可以请求regex的帮助,这里的great

code:

代码:

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'

Output:

输出:

yes  

Hope this helps :)

希望这有助于:)

#8


0  

You could always use a list comprehension and check the results with all, it would be a little less resource intensive than using a regex: all([c in string.letters + string.digits + ["_", "-"] for c in mystring])

您可以使用列表理解并检查所有结果,与使用regex: all(字符串中的[c])相比,它的资源消耗要少一些。字母+字符串。在mystring中c的数字+ ["_","-"])

#9


0  

Here's something based on Jerub's "naive approach" (naive being his words, not mine!):

这是基于Jerub的“天真的方法”(天真是他的话,不是我的!)

import string
ALLOWED = frozenset(string.ascii_letters + string.digits + '_' + '-')

def check(mystring):
    return all(c in ALLOWED for c in mystring)

If ALLOWED was a string then I think c in ALLOWED would involve iterating over each character in the string until it found a match or reached the end. Which, to quote Joel Spolsky, is something of a Shlemiel the Painter algorithm.

如果允许是一个字符串,那么我认为允许的c将涉及到遍历字符串中的每个字符,直到找到匹配或到达末尾。用Joel Spolsky的话来说,这有点像画家算法。

But testing for existence in a set should be more efficient, or at least less dependent on the number of allowed characters. Certainly this approach is a little bit faster on my machine. It's clear and I think it performs plenty well enough for most cases (on my slow machine I can validate tens of thousands of short-ish strings in a fraction of a second). I like it.

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

ACTUALLY on my machine a regexp works out several times faster, and is just as simple as this (arguably simpler). So that probably is the best way forward.

实际上,在我的机器上,regexp运算速度要快几倍,并且与此一样简单(可以说更简单)。所以这可能是最好的方法。

#10


-2  

use a regex and see if it matches!

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

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