在Python中,如何检查字符串是否仅包含特定字符?

91 投票
8 回答
214629 浏览
提问于 2025-04-15 13:50

在Python中,如何检查一个字符串是否只包含特定的字符呢?

我需要检查一个字符串,确保它只包含小写字母a到z、数字0到9,以及一个句号(.),而没有其他任何字符。

我可以逐个检查每个字符,看看它是否是a到z的字母、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 个回答

51

有没有更简单的方法?更符合Python风格的吗?

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

这方法虽然不是最有效率的,但看起来很容易理解。

98

这里有一个简单的纯Python实现。当性能不是特别重要的时候可以使用这个(留给未来的搜索者)。

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

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

关于性能,迭代方法可能是最快的。正则表达式需要通过一个状态机进行迭代,而集合相等的解决方案需要构建一个临时集合。不过,这两者之间的差别可能不会太大。如果这个函数的性能非常重要,可以考虑写成C语言的扩展模块,使用switch语句(这样会编译成跳转表)。

这里有一个C语言的实现,由于空间限制使用了if语句。如果你真的需要那一点点额外的速度,可以写出switch-case。在我的测试中,它的表现非常好(在与正则表达式的基准测试中,耗时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
56

最终(?)编辑

这是一个封装在函数里的答案,附带了注释的互动示例:

>>> 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() 的表现更好;当最终结果为真时,match() 的开销似乎比 search() 大得多,这让人感到困惑(也许是因为返回一个 MatchObject 的成本高于返回 None),这可能需要进一步探讨。

==== Earlier text ====

之前被接受的答案可以做一些改进:

(1) 展示的方式让人感觉像是一个互动的 Python 会话的结果:

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

但是 match() 并没有返回 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

撰写回答