57

这是我检查是否mystring以某个字符串开头的方法:

>>> mystring.lower().startswith("he")
True

问题是它mystring很长(数千个字符),所以lower()操作需要很多时间。

问题:有没有更有效的方法?

我的失败尝试:

>>> import re;
>>> mystring.startswith("he", re.I)
False
4

7 回答 7

63

您可以使用正则表达式,如下所示:

In [33]: bool(re.match('he', 'Hello', re.I))
Out[33]: True 

In [34]: bool(re.match('el', 'Hello', re.I))
Out[34]: False 

在 2000 个字符的字符串上,这比 : 快大约 20 倍lower()

In [38]: s = 'A' * 2000

In [39]: %timeit s.lower().startswith('he')
10000 loops, best of 3: 41.3 us per loop

In [40]: %timeit bool(re.match('el', s, re.I))
100000 loops, best of 3: 2.06 us per loop

如果您重复匹配相同的前缀,预编译正则表达式会产生很大的不同:

In [41]: p = re.compile('he', re.I)

In [42]: %timeit p.match(s)
1000000 loops, best of 3: 351 ns per loop

对于短前缀,在将其转换为小写之前从字符串中切出前缀可能会更快:

In [43]: %timeit s[:2].lower() == 'he'
1000000 loops, best of 3: 287 ns per loop

这些方法的相对时间当然取决于前缀的长度。在我的机器上,盈亏平衡点似乎是大约六个字符,这是预编译正则表达式成为最快方法的时候。

在我的实验中,单独检查每个字符可能会更快:

In [44]: %timeit (s[0] == 'h' or s[0] == 'H') and (s[1] == 'e' or s[1] == 'E')
1000000 loops, best of 3: 189 ns per loop

但是,此方法仅适用于编写代码时已知的前缀,不适用于更长的前缀。

于 2012-11-27T06:59:08.127 回答
32

这个怎么样:

prefix = 'he'
if myVeryLongStr[:len(prefix)].lower() == prefix.lower()
于 2012-11-27T07:02:10.013 回答
8

另一个简单的解决方案是将一个元组传递给startswith()所有需要匹配的情况,例如.startswith(('case1', 'case2', ..)).

例如:

>>> 'Hello'.startswith(('He', 'HE'))
True
>>> 'HEllo'.startswith(('He', 'HE'))
True
>>>
于 2018-12-21T19:23:17.763 回答
3

只要您考虑 ASCII 范围之外的任何内容,给定的答案实际上都不是正确的。

例如,在不区分大小写的比较中,如果您遵循 Unicode 的大小写映射规则ß,则应将其视为等于。SS

要获得正确的结果,最简单的解决方案是安装遵循标准的 Python正则表达式模块:

import re
import regex
# enable new improved engine instead of backwards compatible v0
regex.DEFAULT_VERSION = regex.VERSION1 

print(re.match('ß', 'SS', re.IGNORECASE)) # none
print(regex.match('ß', 'SS', regex.IGNORECASE)) # matches
于 2017-03-22T14:31:26.493 回答
2

根据 .lower() 的性能,如果前缀足够小,则多次检查相等性可能会更快:

s =  'A' * 2000
prefix = 'he'
ch0 = s[0] 
ch1 = s[1]
substr = ch0 == 'h' or ch0 == 'H' and ch1 == 'e' or ch1 == 'E'

时序(使用与 NPE 相同的字符串):

>>> timeit.timeit("ch0 = s[0]; ch1 = s[1]; ch0 == 'h' or ch0 == 'H' and ch1 == 'e' or ch1 == 'E'", "s = 'A' * 2000")
0.2509511683747405

= 0.25 us per loop

与现有方法相比:

>>> timeit.timeit("s.lower().startswith('he')", "s = 'A' * 2000", number=10000)
0.6162763703208611

= 61.63 us per loop

(当然,这很可怕,但如果代码对性能非常关键,那么它可能是值得的)

于 2012-11-27T07:14:13.313 回答
0

在 Python 3.8 中,最快的解决方案涉及切片和比较前缀,如this answer中所建议:

def startswith(a_source: str, a_prefix: str) -> bool:
    source_prefix = a_source[:len(a_prefix)]
    return source_prefix.casefold() == a_prefix.casefold()

第二快的解决方案使用 ctypes(例如_wcsicmp)。注意:这是一个 Windows 示例。

import ctypes.util

libc_name = ctypes.util.find_library('msvcrt')
libc = ctypes.CDLL(libc_name)

libc._wcsicmp.argtypes = (ctypes.c_wchar_p, ctypes.c_wchar_p)

def startswith(a_source: str, a_prefix: str) -> bool:
    source_prefix = a_source[:len(a_prefix)]
    return libc._wcsicmp(source_prefix, a_prefix) == 0

编译re后的解决方案是第三快的解决方案,包括编译成本。如果该regex模块用于完整的 Unicode 支持,则该解决方案会更慢,如this answer中所建议的那样。每个连续匹配的成本与每个 ctypes 调用大致相同。

lower()并且casefold()很昂贵,因为这些函数通过迭代源字符串中的每个字符(不考虑大小写)并相应地映射它们来创建新的 Unicode 字符串。(请参阅:内置函数是如何str.lower()实现的?)在该循环中花费的时间随着每个字符的增加而增加,因此如果您正在处理短前缀和长字符串,请仅在前缀上调用这些函数。

于 2020-07-25T01:27:53.670 回答
0

另外一个选项:

import re
o = re.search('(?i)^we', 'Wednesday')
print(o != None)

https://docs.python.org/library/re.html#re.I

于 2020-08-13T03:12:04.167 回答