首页 / 知识

忽略Python字符串中的大小写

2023-04-15 08:12:00

忽略Python字符串中的大小写

Ignore case in Python strings

本问题已经有最佳答案,请猛点这里访问。

忽略大小写,比较Python中字符串的最简单方法是什么?

当然可以做到(str1.lower()<= str2.lower())等,但这会创建两个额外的临时字符串(明显的alloc / g-c开销)。

我想我正在寻找一个等效于C的stricmp()。

[需要更多上下文,所以我将用一个简单的示例进行演示:]

假设您要排序一个完整的字符串列表。您只需执行List.sort()。
这是O(n * log(n))字符串比较,没有内存管理(因为所有
字符串和列表元素是某种智能指针)。你很快乐。

现在,您想做同样的事情,但是忽略这种情况(让我们简化并说
所有字符串均为ascii,因此可以忽略区域设置问题)。
您可以执行List.sort(key = lambda s:s.lower()),但随后会导致两个新
每个比较的分配,再加上重复的垃圾收集器负担
(降低的)字符串。
每个这样的内存管理噪声都比简单的字符串比较慢几个数量级。

现在,使用类似于stricmp()的函数,您可以执行:theList.sort(cmp = stricmp)
它与List.sort()一样快且对内存友好。你又开心了

问题是任何基于Python的不区分大小写的比较都涉及隐式字符串
复制,所以我期望找到基于C的比较(也许在模块字符串中)。

找不到类似的内容,因此这里出现了问题。
(希望这可以澄清问题)。


这是一个基准测试,显示使用str.lower比接受的答案的建议方法(libc.strcasecmp)更快:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/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))

我机器上的典型时间:

1
2
3
4
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上下文中给您错误的结果。


您的问题暗示您不需要Unicode。试试下面的代码片段;如果它对您有用,那么您已经完成:

1
2
3
4
5
6
7
8
9
10
Python 2.5.2 (r252:60911, Aug 22 2008, 02:34:17)
[GCC 4.3.1] on linux2
Type"help","copyright","credits" or"license" for more information.
>>> import locale
>>> locale.setlocale(locale.LC_COLLATE,"en_US")
'en_US'
>>> sorted("ABCabc", key=locale.strxfrm)
['a', 'A', 'b', 'B', 'c', 'C']
>>> sorted("ABCabc", cmp=locale.strcoll)
['a', 'A', 'b', 'B', 'c', 'C']

说明:如果乍一看不太明显,则locale.strcoll似乎是您需要的功能,避免使用str.lower或locale.strxfrm"重复"字符串。


您是否在对性能敏感的应用程序经常执行的路径中使用此比较?或者,您是否在大小为兆字节的字符串上运行此代码?如果不是,那么您不必担心性能,而只需使用.lower()方法。

下面的代码演示了如何通过在两个1.8GHz台式机上调用.lower()两个字符串来进行大小写不敏感的比较,这两个字符串的大小几乎都是兆字节,大约需要0.009秒:

1
2
3
4
5
6
7
8
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


我找不到任何其他不区分大小写的内置方法:python Cookbook食谱使用lower()。

但是,由于土耳其语I问题,在使用更低的进行比较时必须小心。不幸的是,Python对于"土耳其语"的处理不好。 ?被转换为I,但我未转换为?。 ?被转换为i,但我未被转换为?。


没有与您想要的功能等效的内置功能。

您可以编写自己的函数,一次将每个字符转换为.lower(),以避免重复两个字符串,但是我确信它会占用大量CPU并且效率极低。

除非您使用极长的字符串(如果重复使用太长的字符串,可能会导致内存问题),否则我将使其简单易用

1
str1.lower() == str2.lower()

你会没事的


这个问题问的是两个截然不同的问题:

  • 忽略大小写,比较Python中字符串的最简单方法是什么?
  • 我想我正在寻找一个等效于C的stricmp()。
  • 由于已经很好地回答了#1(即:str1.lower()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    def strincmp(str1, str2, numchars=None):
        result = 0
        len1 = len(str1)
        len2 = len(str2)
        if numchars is not None:
            minlen = min(len1,len2,numchars)
        else:
            minlen = min(len1,len2)
        #end if
        orda = ord('a')
        ordz = ord('z')

        i = 0
        while i < minlen and 0 == result:
            ord1 = ord(str1[i])
            ord2 = ord(str2[i])
            if ord1 >= orda and ord1 <= ordz:
                ord1 = ord1-32
            #end if
            if ord2 >= orda and ord2 <= ordz:
                ord2 = ord2-32
            #end if
            result = cmp(ord1, ord2)
            i += 1
        #end while

        if 0 == result and minlen != numchars:
            if len1 < len2:
                result = -1
            elif len2 < len1:
                result = 1
            #end if
        #end if

        return result
    #end def

    仅在有意义的情况下才使用此功能,因为在许多情况下,小写技术将是更好的选择。

    我只使用ascii字符串,我不确定unicode会如何表现。


    如果标准库中对某些内容的支持不佳,我总是会寻找PyPI软件包。随着虚拟化和现代Linux发行版的普及,我不再避免使用Python扩展。 PyICU似乎符合要求:https://stackoverflow.com/a/1098160/3461

    现在还有一个纯python选项。经过充分测试的:https://github.com/jtauber/pyuca

    旧答案:

    我喜欢正则表达式解决方案。这是一个可以复制和粘贴到任何函数中的函数,这要归功于python对块结构的支持。

    1
    2
    3
    def equals_ignore_case(str1, str2):
        import re
        return re.match(re.escape(str1) + r'\Z', str2, re.I) is not None

    由于我使用匹配而不是搜索,因此无需在正则表达式中添加插入符号(^)。

    注意:这仅检查相等性,有时这是必需的。我也不会说我喜欢它。


    这是您如何使用re进行操作:

    1
    2
    3
    4
    5
    import re
    p = re.compile('^hello$', re.I)
    p.match('Hello')
    p.match('hello')
    p.match('HELLO')

    建议使用昂贵的计算键对值列表进行排序的惯用法是所谓的"装饰模式"。它仅包括从原始列表中构建(键,值)元组的列表,然后对该列表进行排序。然后,消除键并获得排序值列表很简单:

    1
    2
    3
    4
    5
    6
    >>> original_list = ['a', 'b', 'A', 'B']
    >>> decorated = [(s.lower(), s) for s in original_list]
    >>> decorated.sort()
    >>> sorted_list = [s[1] for s in decorated]
    >>> sorted_list
    ['A', 'a', 'B', 'b']

    或者,如果您喜欢单线:

    1
    2
    3
    >>> sorted_list = [s[1] for s in sorted((s.lower(), s) for s in original_list)]
    >>> sorted_list
    ['A', 'a', 'B', 'b']

    如果您真的担心调用lower()的开销,则只需在任何地方存储(低位字符串,原始字符串)元组。元组是Python中最便宜的一种容器,它们也是可哈希的,因此可以用作字典键,集合成员等。


    对于偶尔甚至重复的比较,只要在核心代码的最内层循环中不会发生或者您没有足够的数据实际注意到性能影响,几个额外的字符串对象就无关紧要。看看你是否这样做:以"愚蠢"的方式做事要愚蠢得多,如果你做的也更少。

    如果您真的想不区分大小写地比较很多文本,则可以以某种方式保留字符串的小写形式,以避免最终确定和重新创建,或者将整个数据集标准化为小写形式。当然,这取决于数据集的大小。如果针头相对较少且干草堆较大,则用已编译的正则表达式对象替换针头是一种解决方案。如果不看具体例子就很难说。


    我很确定您必须使用.lower()或使用正则表达式。我不知道内置的不区分大小写的字符串比较功能。


    您可以只在需要时才将每个字符串一次转换为小写字母,或者仅在需要时才将其转换为小写字母;如果知道要对整个字符串集合进行排序,则可以将其转换为小写。有几种方法可以将此比较键附加到要排序的实际数据上,但是这些技术应在单独的问题中解决。

    请注意,此技术不仅可以用于处理大小写问题,还可以用于其他类型的排序,例如特定于语言环境的排序,或"库样式"标题排序,而忽略标题,并在排序之前对数据进行规范化。


    1
    2
    3
    import re
    if re.match('tEXT', 'text', re.IGNORECASE):
        # is True

    只需使用str().lower()方法,除非高性能很重要-在这种情况下,应将该排序方法编写为C扩展。

    "如何编写Python扩展"似乎是不错的介绍。

    更有趣的是,本指南将使用ctypes库与编写外部C模块进行了比较(ctype比C扩展慢得多)。


    您可以将str子类化,并创建自己的区分大小写的字符串类,但是恕我直言,这是极其不明智的做法,并且会产生超出其价值的麻烦。


    回应您的澄清...

    您可以使用ctypes执行c函数" strcasecmp"。 Ctypes包含在Python 2.5中。它提供了调出dll和共享库(如libc)的功能。这是一个简单的示例(Linux上的Python;有关Win32帮助的链接,请参见):

    1
    2
    3
    4
    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代码-食谱194371:不区分大小写的字符串
    是创建不区分大小写的字符串类的方法。快速执行某些操作可能会导致终止,但是如果您打算经常使用它们,可以为您提供一种处理不区分大小写的字符串的通用方法。


    字符串大小写方法排序

    最新内容

    相关内容

    猜你喜欢