在Python中,如何检查字符串是否仅包含特定字符?
在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 个回答
有没有更简单的方法?更符合Python风格的吗?
>>> ok = "0123456789abcdef"
>>> all(c in ok for c in "123456abc")
True
>>> all(c in ok for c in "hello world")
False
这方法虽然不是最有效率的,但看起来很容易理解。
这里有一个简单的纯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
最终(?)编辑
这是一个封装在函数里的答案,附带了注释的互动示例:
>>> 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