【问题标题】:Ignore case in Python strings [duplicate]忽略Python字符串中的大小写[重复]
【发布时间】:2010-09-08 22:05:00
【问题描述】:

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

当然可以执行 (str1.lower()

我想我正在寻找与 C 的 stricmp() 等效的方法。

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

假设你想对一个很长的字符串列表进行排序。您只需执行 List.sort()。 这是 O(n * log(n)) 字符串比较并且没有内存管理(因为所有 字符串和列表元素是某种智能指针)。你很开心。

现在,您也想这样做,但忽略大小写(让我们简化并说 所有字符串都是 ascii,因此可以忽略语言环境问题)。 你可以做 theList.sort(key=lambda s: s.lower()),但是你会导致两个新的 每次比较的分配,加上重复的垃圾收集器的负担 (降低)字符串。 每个这样的内存管理噪音都比简单的字符串比较慢几个数量级。

现在,使用类似 stricmp() 的就地函数,您可以执行以下操作:theList.sort(cmp=stricmp) 它与 theList.sort() 一样快速且内存友好。你又快乐了。

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

找不到类似的东西,因此这里的问题。 (希望这可以澄清问题)。

【问题讨论】:

  • PHP 等效项:strcasecmp - nl3.php.net/strcasecmp
  • 你的假设是错误的。 list.sort() with a key= not 表示“每次比较两个新分配”。 (另一方面,使用 cmp= 的 list.sort 确实调用每个比较的参数)
  • 尝试将问题从 Ignore case in python strings 重命名为 What's closest to stricmp in Python for 7-bit ascii string comparison? 以更准确地反映操作员的实际问题。主要问题:unicode 也是“字符串”,但这个问题会让他们 totally 出错;见 tchrist 的 cmets

标签: python string case-insensitive


【解决方案1】:

这是一个基准,显示使用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 上下文中给出错误的结果。

【讨论】:

  • 当然内存是个问题,因为超过 99.9% 的 .lower() 时间是内存分配。此外,在我检查过的(windows)机器上,key=_stricmp 方法快了 4-5 倍,而且没有内存问题。
  • 比 .lower-method 快 4-5 倍意味着它比简单排序案例快 2 倍。怎么可能?!?
  • 这也是错误的,因为如果你不使用Unicode casefolds,你会得到各种各样的错误答案。
  • @hop:检查 bugs.python.org 是否有 Unicode 错误。我刚刚提出了一堆测试用例,展示了 Python 在哪些地方因为不使用 casefolding 而搞砸了。如果我必须在快速和正确之间做出选择,我知道我每次都会选择哪一个。
  • 最好避免将别人的回答称为“愚蠢”。
【解决方案2】:

您的问题暗示您不需要 Unicode。试试下面的代码 sn-p;如果它对你有用,你就完成了:

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 “重复”字符串。

【讨论】:

  • locale.setlocale() 的全局设置显然是矫枉过正(太全局了)。
  • 我不知道“明显的矫枉过正”是什么,并且“全局”设置可以随心所欲地进行本地化(除非您使用线程并且需要一些线程本地化而一些不需要,出于某种原因)。
  • 这是唯一产生的结果可以与不区分大小写的实用程序正确互操作的解决方案,例如带有 -f 选项的 Unix 排序。例如,str.lower 导致 A_ 在 AA 之前排序。
  • 您不能使用 POSIX 语言环境和 strcoll,因为它跨平台不可靠。您必须使用 Unicode 大小写,保证在任何地方都一样。
【解决方案3】:

您是否在高性能敏感应用程序的非常频繁执行的路径中使用此比较?或者,您是否在兆字节大小的字符串上运行它?如果没有,那么您不必担心性能,只需使用 .lower() 方法即可。

以下代码演示了在我的 1.8GHz 台式计算机上,通过调用 .lower() 对两个大小几乎为 1 MB 的字符串进行不区分大小写的比较大约需要 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

【讨论】:

  • 这就是你将东西传递给 Timer 类的方式。感谢您解决了我的一个非常不同的痒:)
  • 这是完全错误的。它无法检测到 ΣΤΙΓΜΑΣστιγμας 在不敏感的情况下是相同的情况。您不能使用大小写映射来比较 Unicode 中的大小写。您必须使用casefolding。这些是不同的东西。 Σ, σ, ς 都是一样的,就像 S, ſ, s (s 到底是什么?:) 和 Μ, μ, µ 是。可能还有无数其他类似的情况,例如 weiß, WEIẞ, weiss, WEISS 也都相同,或者 efficient,高效。必须使用 casefolds, 因为案例地图不起作用。
【解决方案4】:

我找不到任何其他内置方式来进行不区分大小写的比较:python cook-book recipe 使用 lower()。

但是,由于Turkish I problem,在使用 lower 进行比较时必须小心。不幸的是,Python 对土耳其语的处理并不好。 ı 转换为 I,但 I 未转换为 ı。 İ 转换为 i,但 i 未转换为 İ。

【讨论】:

  • 如您所见,Python 处理 Unicode 的能力不是很强。案例地图不关注这些事情。很伤心。
【解决方案5】:

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

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

除非你正在处理非常长的字符串(如果重复的话会导致内存问题),那么我会保持简单并使用

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

你会没事的

【讨论】:

  • “永不言败” :) “没有内置的等价物”是绝对的; “我知道没有内置的等价物”会更接近事实。 locale.strcoll,给定一个不区分大小写的 LC_COLLATE(就像 'en_US' 一样),是一个内置的。
  • 这个答案是错误的。唯一正确的方法是str1.fold() == str2.fold(),但这需要对支持字符串的完整 Unicode 大小写折叠的默认 python 字符串类进行扩展。这是一个缺失的功能。
  • @tchristclearr:有这样的扩展可用吗?
【解决方案6】:

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

  1. 在 Python 中忽略大小写比较字符串的最简单方法是什么?
  2. 我想我正在寻找与 C 的 stricmp() 等效的方法。

由于#1 已经得到很好的回答(即:str1.lower()

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。

【讨论】:

    【解决方案7】:

    当标准库不能很好地支持某些东西时,我总是寻找 PyPI 包。随着虚拟化和现代 Linux 发行版的普及,我不再避免使用 Python 扩展。 PyICU 似乎符合要求:https://stackoverflow.com/a/1098160/3461

    现在还有一个选项是纯 python。已经测试过了:https://github.com/jtauber/pyuca


    旧答案:

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

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

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

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

    【讨论】:

    • [我希望有一个虚拟橡皮图章] 不要使用$,使用\Z。阅读精彩的手册以了解 $ 的实际作用;不要依赖传说或猜测或其他任何东西。
    • 我改了。我还为我的答案打开了社区 wiki 功能。谢谢。
    • 仅适用于相等性测试,这与比较两个字符串并确定一个小于、等于还是大于另一个字符串完全不同。
    • @martineau 谢谢。我添加了一条注释,并进行了一些搜索并找到了一个我认为我会更舒服的解决方案,并用它更新了我的答案。不过,这不是一个完整的答案。希望有人(如果我能找到它,我自己)将了解其中一个库的工作原理并提供代码示例。
    • 是的,听起来 pyuca(Python Unicode 排序算法)扩展可能会起作用,因为它所基于的报告——Unicode Collation Algorithm (UCA)——说“大小写差异(大写与小写)通常是忽略”。
    【解决方案8】:

    这就是你如何使用 re:

    import re
    p = re.compile('^hello$', re.I)
    p.match('Hello')
    p.match('hello')
    p.match('HELLO')
    

    【讨论】:

    • 不区分大小写的正则表达式只能用于相等测试(真/假),不能用于比较(小于/等于/大于)
    【解决方案9】:

    推荐使用计算成本高的键对值列表进行排序的习惯用法是所谓的“装饰模式”。它只是从原始列表中构建一个(键,值)元组列表,然后对该列表进行排序。然后就可以简单地消除键并获得排序后的值列表:

    >>> 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']
    

    或者如果你喜欢单行:

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

    如果您真的担心调用 lower() 的成本,您可以在任何地方存储 (lowered string, original string) 的元组。元组是 Python 中最便宜的一种容器,它们也是可散列的,因此可以用作字典键、集合成员等。

    【讨论】:

    • 元组很便宜,但字符串的重复不是...
    • 这也是 python 使用 key= 参数的排序所做的。
    • 这是一种完全不适合 Unicode 数据的 7 位思维模式。您必须使用完整的 Unicode 大小写,或者使用 Unicode 排序算法的主要排序强度。是的,无论哪种方式,这都意味着字符串的新副本,但至少您可以进行二进制比较,而不必翻找每个代码点的表。
    【解决方案10】:

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

    【讨论】:

      【解决方案11】:

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

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

      【讨论】:

        【解决方案12】:

        您可以将每个字符串转换为小写一次 --- 仅在需要时懒惰地转换,或者如果您知道要对整个字符串集合进行排序,则作为排序的预通行证。有多种方法可以将此比较键附加到正在排序的实际数据,但这些技术应在单独的问题中解决。

        请注意,此技术不仅可用于处理大写/小写问题,还可用于其他类型的排序,例如特定于区域设置的排序,或忽略前导文章并以其他方式规范化之前数据的“图书馆风格”标题排序排序。

        【讨论】:

        • 这个问题比示例本身更笼统(实际上,在现实生活场景中,您不想为以后可能需要 icmp() 的每个字符串附加小写版本而烦恼),但是即使在这个微不足道的例子中,你也不想为了能够排序而将内存加倍......
        【解决方案13】:

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

        "How to write a Python Extension" 似乎是一个不错的介绍......

        更有趣的是,This guide 比较了使用 ctypes 库与编写外部 C 模块(ctype 比 C 扩展慢得多)。

        【讨论】:

          【解决方案14】:
          import re
          if re.match('tEXT', 'text', re.IGNORECASE):
              # is True
          

          【讨论】:

            【解决方案15】:

            您可以继承 str 并创建自己的不区分大小写的字符串类,但恕我直言,这将是非常不明智的,并且会造成比其价值更多的麻烦。

            【讨论】:

              【解决方案16】:

              响应您的澄清...

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

              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 documentation

              不确定这是更快或更慢(尚未测试),但它是一种使用 C 函数进行不区分大小写的字符串比较的方法。

              ~~~~~~~~~~~~~~

              ActiveState Code - Recipe 194371: Case Insensitive Strings 是创建不区分大小写的字符串类的秘诀。这可能有点过头了,但如果你打算经常使用它们,它可以为你提供一种处理不区分大小写字符串的常用方法。

              【讨论】:

              • 我很了解这个配方,但在幕后它只是为每个字符串都有一个小写的副本,这是不好的(正如我添加的简单示例中所解释的)
              • ctype 解决方案正是我想要的,谢谢。作为参考,这里是win32代码: from ctypes import * clib = cdll.LoadLibrary("msvcrt") theList = ["abc","ABC","def","DEF"] * 1000000 theList.sort(cmp = clib._stricmp)
              • 这要慢得多。看我的回答!
              • 我相信这对带有空值的字符串给出了错误的答案。
              • 不,这都是错误的。唯一正确的解决方案是比较他们的 Unicode casefolds。否则你会搞砸的。
              猜你喜欢
              • 2012-12-10
              • 1970-01-01
              • 2013-05-12
              • 2015-09-12
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2016-11-11
              • 1970-01-01
              相关资源
              最近更新 更多