在Python字符串中忽略大小写
在Python中,最简单的方法来比较字符串而不考虑大小写是什么呢?
当然,你可以用 (str1.lower() <= str2.lower()) 这样的方式来比较,但这样会产生两个额外的临时字符串,这样会消耗更多的内存和处理时间。
我想找一个类似于C语言中stricmp()的功能。
[有人要求更多的背景信息,所以我用一个简单的例子来说明:]
假设你想对一长串字符串进行排序。你只需要用 theList.sort() 就可以了。这会进行 O(n * log(n)) 次字符串比较,而且不需要管理内存(因为所有字符串和列表元素都是某种智能指针)。这样你就很满意了。
现在,你想做同样的事情,但忽略大小写(为了简单起见,我们假设所有字符串都是ASCII格式,所以不需要考虑地区问题)。你可以用 theList.sort(key=lambda s: s.lower()),但这样每次比较都会产生两个新的字符串,还会让垃圾回收器处理这些重复的(小写的)字符串。每次这样的内存管理都比简单的字符串比较慢得多。
如果有一个类似于stricmp()的函数,你就可以用 theList.sort(cmp=stricmp) 来排序,这样既快又省内存,你又会很开心。
问题是,任何基于Python的大小写不敏感的比较都会涉及到隐式的字符串复制,所以我本来期待能找到一个基于C的比较方法(也许在string模块里)。
但我没有找到这样的东西,所以才有了这个问题。(希望这能澄清我的问题)。
16 个回答
你是在一个需要高性能的应用程序中,频繁使用这个比较操作吗?或者说,你是在处理几兆字节大小的字符串吗?如果不是,那就不用担心性能问题,直接使用 .lower() 方法就可以了。
下面的代码演示了在我的1.8GHz桌面电脑上,对两个接近一兆字节大小的字符串进行不区分大小写的比较,调用 .lower() 方法大约只需要0.009秒:
from timeit import Timer
s1 = "1234567890" * 100000 + "a"
s2 = "1234567890" * 100000 + "B"
code = "s1.lower() < s2.lower()"
time = Timer(code, "from __main__ import s1, s2").timeit(1000)
print time / 1000 # 0.00920499992371 on my machine
如果这个代码确实是一个非常重要、对性能要求极高的部分,那么我建议你用C语言写一个函数,然后在Python代码中调用它,这样可以实现真正高效的不区分大小写的搜索。关于如何编写C扩展模块的详细信息,可以在这里找到:https://docs.python.org/extending/extending.html
这里有一个基准测试,显示使用 str.lower
的速度比被接受的答案中提到的方法 (libc.strcasecmp
) 快很多:
#!/usr/bin/env python2.7
import random
import timeit
from ctypes import *
libc = CDLL('libc.dylib') # change to 'libc.so.6' on linux
with open('/usr/share/dict/words', 'r') as wordlist:
words = wordlist.read().splitlines()
random.shuffle(words)
print '%i words in list' % len(words)
setup = 'from __main__ import words, libc; gc.enable()'
stmts = [
('simple sort', 'sorted(words)'),
('sort with key=str.lower', 'sorted(words, key=str.lower)'),
('sort with cmp=libc.strcasecmp', 'sorted(words, cmp=libc.strcasecmp)'),
]
for (comment, stmt) in stmts:
t = timeit.Timer(stmt=stmt, setup=setup)
print '%s: %.2f msec/pass' % (comment, (1000*t.timeit(10)/10))
在我的机器上,典型的运行时间是:
235886 words in list
simple sort: 483.59 msec/pass
sort with key=str.lower: 1064.70 msec/pass
sort with cmp=libc.strcasecmp: 5487.86 msec/pass
所以,使用 str.lower
的版本不仅速度最快,而且在所有提议的解决方案中,最具可移植性和符合 Python 风格。我没有分析内存使用情况,但原帖作者至今没有给出令人信服的理由让人担心这个问题。而且,谁说调用 libc 模块就不会重复字符串呢?
注意:lower()
字符串方法还有一个好处,就是它会根据地区设置而变化。这一点在你自己写“优化”解决方案时可能不会考虑到。尽管如此,由于 Python 中的错误和缺失功能,这种比较在处理 Unicode 时可能会给出错误的结果。
关于你的澄清...
你可以使用 ctypes 来调用 C 语言的函数 "strcasecmp"。ctypes 是从 Python 2.5 开始就有的一个功能。它可以让你调用一些动态链接库和共享库,比如 libc。下面是一个简单的例子(这是在 Linux 上的 Python;如果你需要 Windows 的帮助,可以查看链接):
from ctypes import *
libc = CDLL("libc.so.6") // see link above for Win32 help
libc.strcasecmp("THIS", "this") // returns 0
libc.strcasecmp("THIS", "THAT") // returns 8
你可能还想看看 strcasecmp 的文档
我不太确定这样做是否更快或更慢(我没有测试过),但这是使用 C 函数进行不区分大小写的字符串比较的一种方法。
~~~~~~~~~~~~~~
ActiveState Code - Recipe 194371: 不区分大小写的字符串 是一个创建不区分大小写字符串类的示例。虽然这个方法可能有点复杂,但如果你打算经常使用不区分大小写的字符串,它可以为你提供一个常见的处理方式。