在Python中,如何检查字符串是否只包含某些字符?

时间:2009-08-24 16:22:06

标签: python regex search character

在Python中,如何检查字符串是否只包含某些字符?

我需要检查一个只包含a..z,0..9和的字符串。 (期间),没有其他人物。

我可以遍历每个角色并检查角色是......或0..9,或者。但那会很慢。

我现在还不清楚如何使用正则表达式。

这是对的吗?您能否建议更简单的正则表达式或更有效的方法。

#Valid chars . a-z 0-9 
def check(test_str):
    import re
    #http://docs.python.org/library/re.html
    #re.search returns None if no position in the string matches the pattern
    #pattern to search for any character other then . a-z 0-9
    pattern = r'[^\.a-z0-9]'
    if re.search(pattern, test_str):
        #Character other then . a-z 0-9 was found
        print 'Invalid : %r' % (test_str,)
    else:
        #No character other then . a-z 0-9 was found
        print 'Valid   : %r' % (test_str,)

check(test_str='abcde.1')
check(test_str='abcde.1#')
check(test_str='ABCDE.12')
check(test_str='_-/>"!@#12345abcde<')

'''
Output:
>>> 
Valid   : "abcde.1"
Invalid : "abcde.1#"
Invalid : "ABCDE.12"
Invalid : "_-/>"!@#12345abcde<"
'''

8 个答案:

答案 0 :(得分:56)

这是一个简单的纯Python实现。它应该在性能不重要时使用(包含在未来的Google员工中)。

import string
allowed = set(string.ascii_lowercase + string.digits + '.')

def check(test_str):
    set(test_str) <= allowed

关于性能,迭代可能是最快的方法。正则表达式必须遍历状态机,并且集合相等解决方案必须构建临时集。但是,差异不大可能太重要。如果此函数的性能非常重要,请将其写为带有switch语句的C扩展模块(将编译为跳转表)。

这是一个C实现,由于空间限制而使用if语句。如果您绝对需要一点点额外的速度,请写出开关盒。在我的测试中,它表现得非常好(对正则表达式的基准测试中,2秒对9秒)。

#define PY_SSIZE_T_CLEAN
#include <Python.h>

static PyObject *check(PyObject *self, PyObject *args)
{
        const char *s;
        Py_ssize_t count, ii;
        char c;
        if (0 == PyArg_ParseTuple (args, "s#", &s, &count)) {
                return NULL;
        }
        for (ii = 0; ii < count; ii++) {
                c = s[ii];
                if ((c < '0' && c != '.') || c > 'z') {
                        Py_RETURN_FALSE;
                }
                if (c > '9' && c < 'a') {
                        Py_RETURN_FALSE;
                }
        }

        Py_RETURN_TRUE;
}

PyDoc_STRVAR (DOC, "Fast stringcheck");
static PyMethodDef PROCEDURES[] = {
        {"check", (PyCFunction) (check), METH_VARARGS, NULL},
        {NULL, NULL}
};
PyMODINIT_FUNC
initstringcheck (void) {
        Py_InitModule3 ("stringcheck", PROCEDURES, DOC);
}

将其包含在setup.py中:

from distutils.core import setup, Extension
ext_modules = [
    Extension ('stringcheck', ['stringcheck.c']),
],

用作:

>>> from stringcheck import check
>>> check("abc")
True
>>> check("ABC")
False

答案 1 :(得分:31)

最终(?)修改

使用带注释的交互式会话回答,包含在函数中:

>>> import re
>>> def special_match(strg, search=re.compile(r'[^a-z0-9.]').search):
...     return not bool(search(strg))
...
>>> special_match("")
True
>>> special_match("az09.")
True
>>> special_match("az09.\n")
False
# The above test case is to catch out any attempt to use re.match()
# with a `$` instead of `\Z` -- see point (6) below.
>>> special_match("az09.#")
False
>>> special_match("az09.X")
False
>>>

注意:在此答案中进一步使用re.match()进行比较。进一步的时间表明match()将赢得更长的字符串;当最终答案为True时,match()似乎比search()有更大的开销;这很令人费解(也许是返回MatchObject而不是None的成本)并且可能需要进一步翻找。

==== Earlier text ====

[之前]接受的答案可以使用一些改进:

(1)Presentation表示是交互式Python会话的结果:

reg=re.compile('^[a-z0-9\.]+$')
>>>reg.match('jsdlfjdsf12324..3432jsdflsdf')
True

但匹配()不会返回True

(2)对于match(),模式开头的^是多余的,并且看起来比没有^

的相同模式稍慢

(3)应该不经思考地为任何重新模式促进原始字符串的使用

(4)点/周期前面的反斜杠是多余的

(5)比OP的代码慢!

prompt>rem OP's version -- NOTE: OP used raw string!

prompt>\python26\python -mtimeit -s"t='jsdlfjdsf12324..3432jsdflsdf';import
re;reg=re.compile(r'[^a-z0-9\.]')" "not bool(reg.search(t))"
1000000 loops, best of 3: 1.43 usec per loop

prompt>rem OP's version w/o backslash

prompt>\python26\python -mtimeit -s"t='jsdlfjdsf12324..3432jsdflsdf';import
re;reg=re.compile(r'[^a-z0-9.]')" "not bool(reg.search(t))"
1000000 loops, best of 3: 1.44 usec per loop

prompt>rem cleaned-up version of accepted answer

prompt>\python26\python -mtimeit -s"t='jsdlfjdsf12324..3432jsdflsdf';import
re;reg=re.compile(r'[a-z0-9.]+\Z')" "bool(reg.match(t))"
100000 loops, best of 3: 2.07 usec per loop

prompt>rem accepted answer

prompt>\python26\python -mtimeit -s"t='jsdlfjdsf12324..3432jsdflsdf';import
re;reg=re.compile('^[a-z0-9\.]+$')" "bool(reg.match(t))"
100000 loops, best of 3: 2.08 usec per loop

(6)会产生错误答案!!

>>> import re
>>> bool(re.compile('^[a-z0-9\.]+$').match('1234\n'))
True # uh-oh
>>> bool(re.compile('^[a-z0-9\.]+\Z').match('1234\n'))
False

答案 2 :(得分:27)

更简单的方法?多一点Pythonic?

>>> ok = "0123456789abcdef"
>>> all(c in ok for c in "123456abc")
True
>>> all(c in ok for c in "hello world")
False

它当然不是最有效的,但它确实可读。

答案 3 :(得分:13)

编辑:更改正则表达式以排除A-Z

正则表达式解决方案是迄今为止最快的纯Python解决方案

reg=re.compile('^[a-z0-9\.]+$')
>>>reg.match('jsdlfjdsf12324..3432jsdflsdf')
True
>>> timeit.Timer("reg.match('jsdlfjdsf12324..3432jsdflsdf')", "import re; reg=re.compile('^[a-z0-9\.]+$')").timeit()
0.70509696006774902

与其他解决方案相比:

>>> timeit.Timer("set('jsdlfjdsf12324..3432jsdflsdf') <= allowed", "import string; allowed = set(string.ascii_lowercase + string.digits + '.')").timeit()
3.2119350433349609
>>> timeit.Timer("all(c in allowed for c in 'jsdlfjdsf12324..3432jsdflsdf')", "import string; allowed = set(string.ascii_lowercase + string.digits + '.')").timeit()
6.7066690921783447

如果要允许空字符串,请将其更改为:

reg=re.compile('^[a-z0-9\.]*$')
>>>reg.match('')
False

根据要求,我将返回答案的其他部分。但请注意以下接受A-Z范围。

您可以使用isalnum

test_str.replace('.', '').isalnum()

>>> 'test123.3'.replace('.', '').isalnum()
True
>>> 'test123-3'.replace('.', '').isalnum()
False

编辑使用isalnum比设置解决方案更有效

>>> timeit.Timer("'jsdlfjdsf12324..3432jsdflsdf'.replace('.', '').isalnum()").timeit()
0.63245487213134766

<强> EDIT2     约翰举了一个例子,上面的内容不起作用。我通过使用encode

更改了解决方案以克服这种特殊情况
test_str.replace('.', '').encode('ascii', 'replace').isalnum()

它仍然比设定的解决方案快3倍

timeit.Timer("u'ABC\u0131\u0661'.encode('ascii', 'replace').replace('.','').isalnum()", "import string; allowed = set(string.ascii_lowercase + string.digits + '.')").timeit()
1.5719811916351318

在我看来,使用正则表达式是解决此问题的最佳方法

答案 4 :(得分:2)

这已经得到了令人满意的回答,但对于事后发生这种情况的人来说,我已经对几种不同的方法进行了一些分析。在我的情况下,我想要大写的十六进制数字,所以根据需要进行修改。

以下是我的测试实现:

import re

hex_digits = set("ABCDEF1234567890")
hex_match = re.compile(r'^[A-F0-9]+\Z')
hex_search = re.compile(r'[^A-F0-9]')

def test_set(input):
    return set(input) <= hex_digits

def test_not_any(input):
    return not any(c not in hex_digits for c in input)

def test_re_match1(input):
    return bool(re.compile(r'^[A-F0-9]+\Z').match(input))

def test_re_match2(input):
    return bool(hex_match.match(input))

def test_re_match3(input):
    return bool(re.match(r'^[A-F0-9]+\Z', input))

def test_re_search1(input):
    return not bool(re.compile(r'[^A-F0-9]').search(input))

def test_re_search2(input):
    return not bool(hex_search.search(input))

def test_re_search3(input):
    return not bool(re.match(r'[^A-F0-9]', input))

测试,在Mac OS X上的Python 3.4.0中:

import cProfile
import pstats
import random

# generate a list of 10000 random hex strings between 10 and 10009 characters long
# this takes a little time; be patient
tests = [ ''.join(random.choice("ABCDEF1234567890") for _ in range(l)) for l in range(10, 10010) ]

# set up profiling, then start collecting stats
test_pr = cProfile.Profile(timeunit=0.000001)
test_pr.enable()

# run the test functions against each item in tests. 
# this takes a little time; be patient
for t in tests:
    for tf in [test_set, test_not_any, 
               test_re_match1, test_re_match2, test_re_match3,
               test_re_search1, test_re_search2, test_re_search3]:
        _ = tf(t)

# stop collecting stats
test_pr.disable()

# we create our own pstats.Stats object to filter 
# out some stuff we don't care about seeing
test_stats = pstats.Stats(test_pr)

# normally, stats are printed with the format %8.3f, 
# but I want more significant digits
# so this monkey patch handles that
def _f8(x):
    return "%11.6f" % x

def _print_title(self):
    print('   ncalls     tottime     percall     cumtime     percall', end=' ', file=self.stream)
    print('filename:lineno(function)', file=self.stream)

pstats.f8 = _f8
pstats.Stats.print_title = _print_title

# sort by cumulative time (then secondary sort by name), ascending
# then print only our test implementation function calls:
test_stats.sort_stats('cumtime', 'name').reverse_order().print_stats("test_*")

给出了以下结果:

         50335004 function calls in 13.428 seconds

   Ordered by: cumulative time, function name
   List reduced from 20 to 8 due to restriction 

   ncalls     tottime     percall     cumtime     percall filename:lineno(function)
    10000    0.005233    0.000001    0.367360    0.000037 :1(test_re_match2)
    10000    0.006248    0.000001    0.378853    0.000038 :1(test_re_match3)
    10000    0.010710    0.000001    0.395770    0.000040 :1(test_re_match1)
    10000    0.004578    0.000000    0.467386    0.000047 :1(test_re_search2)
    10000    0.005994    0.000001    0.475329    0.000048 :1(test_re_search3)
    10000    0.008100    0.000001    0.482209    0.000048 :1(test_re_search1)
    10000    0.863139    0.000086    0.863139    0.000086 :1(test_set)
    10000    0.007414    0.000001    9.962580    0.000996 :1(test_not_any)

其中:

  
ncalls
调用函数的次数
  
tottime
在给定函数中花费的总时间,不包括对子函数的时间
  
percall
tottime的商除以ncalls
  
cumtime
在此子功能和所有子功能中累计花费的时间
  
percall
cumtime的商除以原始调用

我们实际关心的列是cumtime和percall,因为它显示了从函数进入到退出所花费的实际时间。正如我们所看到的,正则表达式匹配和搜索并没有太大的不同。

如果你每次都编译它,那么更快不打扰编译正则表达式。编译一次比每次快7.5%,但编译速度比没编译快2.5%。

test_set的速度是re_search的两倍,三倍于re_match

test_not_any比test_set

慢了整整数量级

TL; DR :使用re.match或re.search

答案 5 :(得分:0)

当您需要比较hm ...数据集时,请使用python Sets。字符串可以非常快速地表示为字符集。在这里,我测试字符串是否允许使用电话号码。允许第一个字符串,第二个不允许。快速简便地工作。

In [17]: timeit.Timer("allowed = set('0123456789+-() ');p = set('+7(898) 64-901-63 ');p.issubset(allowed)").timeit()

Out[17]: 0.8106249139964348

In [18]: timeit.Timer("allowed = set('0123456789+-() ');p = set('+7(950) 64-901-63 фыв');p.issubset(allowed)").timeit()

Out[18]: 0.9240323599951807

如果可以避免使用正则表达式,请不要使用。

答案 6 :(得分:0)

一种不同的方法,因为在我的情况下,我还需要检查它是否包含某些单词(例如本例中的“ test”),而不是仅包含字符:

input_string = 'abc test'
input_string_test = input_string
allowed_list = ['a', 'b', 'c', 'test', ' ']

for allowed_list_item in allowed_list:
    input_string_test = input_string_test.replace(allowed_list_item, '')

if not input_string_test:
    # test passed

因此,从输入字符串中切出了允许的字符串(字符或单词)。如果输入字符串仅包含允许的字符串,则应保留一个空字符串,因此应传递if not input_string

答案 7 :(得分:-1)

从 3,4 版开始,它变得容易多了。使用 public move = (entities, {touches, time}) => { touches.filter(t => t.type === 'press').forEach(t => { const player = entities['player'] const direction = this.getPlayerMoveDirection(t.event.pageX) const nextFieldId = this.getNextFieldIdByDirection(direction) if (nextFieldId !== this.fieldId) { this.setFieldId(nextFieldId) const nextField = this.game.fields[nextFieldId] const nextFieldXPosition = nextField.getCenter() const newXPosition = direction === 'left' ? (player.body.position.x - nextFieldXPosition) *-1 : nextFieldXPosition - player.body.position.x Matter.Body.translate( player.body, {x: newXPosition, y: 0}); this.playMoveSound() } }) return entities } 函数。

fullmatch