如何进行不区分大小写的字符串比较?


573

如何在Python中进行不区分大小写的字符串比较?

我想以一种非常简单和Pythonic的方式封装对常规字符串与存储库字符串的比较。我还希望能够使用常规python字符串在由字符串散列的字典中查找值。

Answers:


595

假设ASCII字符串:

string1 = 'Hello'
string2 = 'hello'

if string1.lower() == string2.lower():
    print("The strings are the same (case insensitive)")
else:
    print("The strings are NOT the same (case insensitive)")

71
这并不总是有效。例如,请考虑有两个希腊语西格玛,其中一个仅在末尾使用。字符串Σίσυφος(“Sísyphos”,或更好的“ Síſyphos”)具有全部三个:大写在前面,小写的final在末尾,小写的非final在第三个位置。如果您的两个字符串是ΣίσυφοςΣΊΣΥΦΟΣ,则您的方法将失败,因为应该以不区分大小写的方式区分它们。
tchrist

51
@最后两个评论者:我认为假设两个字符串都是ascii字符串是公平的。如果您正在寻找更令人兴奋的答案,我确定它已经存在(或者您可以提出要求)。
哈雷·霍尔科姆

16
问题:'ß'.lower() == 'SS'.lower()是错误的。
kennytm

11
希腊字母不是唯一的特例!在美国英语中,字符“ i”(\ u0069)是字符“ I”(\ u0049)的小写版本。但是,土耳其语(“ tr-TR”)字母包含“带有点的I”字符“İ”(\ u0130),这是“ i”的大写版本,“ I”是“ i的大写版本,不包含”点号字符“ı”(\ u0131)。
Gqqnbig

20
@HarleyHolcombe假设字符串为ascii怎么安全(或公平)?该问题未指定,如果字符串在用户输入的任何位置或显示给用户,则您应该支持国际化。无论如何,新程序员将阅读此书,我们应该给他们真正正确的答案。
伊桑·里索

529

以不区分大小写的方式比较字符串似乎很简单,但事实并非如此。我将使用Python 3,因为Python 2在这里尚未开发。

首先要注意的是,用Unicode删除大小写的转换并非易事。有一些文字text.lower() != text.upper().lower(),例如"ß"

"ß".lower()
#>>> 'ß'

"ß".upper().lower()
#>>> 'ss'

但是,假设您想无休止地比较"BUSSE""Buße"。哎呀,您可能还想比较"BUSSE""BUẞE"相等-这是较新的资本形式。推荐的方式是使用casefold

海峡 折叠()

返回字符串的casefolded副本。折叠的字符串可用于无大小写的匹配。

大小写折叠类似于小写字母,但更具攻击性,因为它旨在消除字符串中的所有大小写区别。[...]

不要只是使用lower。如果casefold不可用,则可以提供.upper().lower()帮助(但只能有所帮助)。

然后,您应该考虑口音。如果您的字体渲染器很好,您可能会认为"ê" == "ê"-但事实并非如此:

"ê" == "ê"
#>>> False

这是因为后者的重音是组合字符。

import unicodedata

[unicodedata.name(char) for char in "ê"]
#>>> ['LATIN SMALL LETTER E WITH CIRCUMFLEX']

[unicodedata.name(char) for char in "ê"]
#>>> ['LATIN SMALL LETTER E', 'COMBINING CIRCUMFLEX ACCENT']

解决此问题的最简单方法是unicodedata.normalize。您可能想使用NFKD规范化,但请随时检查文档。然后一个

unicodedata.normalize("NFKD", "ê") == unicodedata.normalize("NFKD", "ê")
#>>> True

最后,这用函数表示:

import unicodedata

def normalize_caseless(text):
    return unicodedata.normalize("NFKD", text.casefold())

def caseless_equal(left, right):
    return normalize_caseless(left) == normalize_caseless(right)

8
更好的解决方案是对所有字符串进行归一化处理,然后就可以x.casefold() == y.casefold()进行不区分大小写的比较(更重要的是,x == y区分大小写)。
2015年

3
@abarnert实际上,根据上下文,有时最好保留源代码的完整性,但预先的规范化也可以使以后的代码更简单。
Veedrac

3
@Vedrac:你是对的,这并不总是恰当的。如果您需要能够不变地输出原始源代码(例如,因为您在Linux上处理文件名,则NKFC和NKFD都被允许并且明确地假定是不同的),显然您不能在输入时进行转换…
abarnert

7
Unicode标准第3.13节还提供了另外两个定义,以进行无区分大小写的比较:NFD(toCasefold(NFD(str)))两面均为(D146,规范),两面均为(D147,兼容性)NFKD(toCasefold(NFKD(toCasefold(NFD(X)))))。它指出内部NFD仅用于处理某种希腊口音字符。我想这全都是边缘案例。

2
切诺基字母有点有趣,其中casefold()变为大写:>>>“ᏚᎢᎵᎬᎢᎬᏒ”。upper()'ᏚᎢᎵᎬᎢᎬᏒ'>>>“ᏚᎢᎵᎬᎢᎬᏒ”。lower()'ꮪꭲꮅꭼꭲꭼꮢ'>>>“ᏚᎢᎵᎬᎢᎬᏒ” .casefold()'ᏚᎢᎵᎬᎢᎬᏒ'>>>
bortzmeyer

60

使用Python 2,调用.lower()每个字符串或Unicode对象...

string1.lower() == string2.lower()

...将在大多数时间工作,但实际上在@tchrist描述情况下不起作用

假设我们有一个名为的文件,unicode.txt其中包含两个字符串ΣίσυφοςΣΊΣΥΦΟΣ。使用Python 2:

>>> utf8_bytes = open("unicode.txt", 'r').read()
>>> print repr(utf8_bytes)
'\xce\xa3\xce\xaf\xcf\x83\xcf\x85\xcf\x86\xce\xbf\xcf\x82\n\xce\xa3\xce\x8a\xce\xa3\xce\xa5\xce\xa6\xce\x9f\xce\xa3\n'
>>> u = utf8_bytes.decode('utf8')
>>> print u
Σίσυφος
ΣΊΣΥΦΟΣ

>>> first, second = u.splitlines()
>>> print first.lower()
σίσυφος
>>> print second.lower()
σίσυφοσ
>>> first.lower() == second.lower()
False
>>> first.upper() == second.upper()
True

Σ字符有两种小写形式,ς和σ,并且.lower()不区分大小写。

但是,从Python 3开始,所有这三种形式都将解析为ς,并且在两个字符串上调用lower()都可以正常工作:

>>> s = open('unicode.txt', encoding='utf8').read()
>>> print(s)
Σίσυφος
ΣΊΣΥΦΟΣ

>>> first, second = s.splitlines()
>>> print(first.lower())
σίσυφος
>>> print(second.lower())
σίσυφος
>>> first.lower() == second.lower()
True
>>> first.upper() == second.upper()
True

因此,如果您关心像希腊语中的三个sigma这样的边缘情况,请使用Python 3。

(供参考,上面的解释器打印输出中显示了Python 2.7.3和Python 3.3.0b1。)


20
为了使比较更加健壮,从Python 3.3开始,您可以使用casefold(例如,first.casefold()== second.casefold())。对于Python 2,您可以使用PyICU(另请参见:icu-project.org/apiref/icu4c/…
kgriffs 2014年

42

Unicode标准的第3.13节定义了无大小写匹配的算法。

X.casefold() == Y.casefold() 在Python 3中实现了“默认无大小写匹配”(D144)。

Casefolding不能在所有实例中保留字符串的规范化,因此需要进行规范化('å'vs. 'å')。D145引入了“规范无大小写匹配”:

import unicodedata

def NFD(text):
    return unicodedata.normalize('NFD', text)

def canonical_caseless(text):
    return NFD(NFD(text).casefold())

NFD() 在涉及U + 0345字符的极少数情况下被调用两次。

例:

>>> 'å'.casefold() == 'å'.casefold()
False
>>> canonical_caseless('å') == canonical_caseless('å')
True

对于'㎒'(U + 3392)和“标识符无例匹配” 等情况,还具有兼容性无例匹配(D146),以简化和优化标识符的无例匹配


3
这是Python 3的最佳答案,因为Python 3使用Unicode字符串,并且答案描述了Unicode标准如何定义无大小写的字符串匹配。
SergiyKolesnikov '16

不幸的是,从Python 3.6开始,该casefold()函数无法实现Case Folding Properties中所述的大写I和点分大写I的特殊情况处理。因此,对于包含这些字母的突厥语单词,比较可能会失败。例如,canonical_caseless('LİMANI') == canonical_caseless('limanı')必须返回True,但必须返回False。当前,用Python处理此问题的唯一方法是编写一个casefold包装器或使用外部Unicode库,例如PyICU。
SergiyKolesnikov '16

据我所知,@SergiyKolesnikov .casefold()表现正常。根据标准:“默认的大小写操作旨在在针对特定语言和环境进行定制的情况下使用”。土耳其点缀大写字母I和无点小写字母i的套管规则在SpecialCasing.txt中。“对于非突厥语,通常不使用此映射。” 从Unicode常见问题解答: 问:为什么没有额外的字符编码来支持土耳其语的独立于语言环境的大小写?
jfs

1
@ jf-sebastian我不是说casefold()行为不当。如果它实现了启用对大写I和点分大写I的特殊处理的可选参数,这将是切实可行的。例如,ICU库中的foldCase()做到这一点的方式:“大小写折叠与语言环境无关,而不是上下文敏感,但可以选择是否为CaseFolding.txt中标记为“ T”的点划线I和无点划线i包含或排除映射。”
SergiyKolesnikov '16

6

我在这里使用regex看到了这个解决方案。

import re
if re.search('mandy', 'Mandy Pande', re.IGNORECASE):
# is True

与重音搭配效果很好

In [42]: if re.search("ê","ê", re.IGNORECASE):
....:        print(1)
....:
1

但是,它不适用于不区分大小写的Unicode字符。谢谢@Rhymoid指出,根据我的理解,对于情况,它需要确切的符号。输出如下:

In [36]: "ß".lower()
Out[36]: 'ß'
In [37]: "ß".upper()
Out[37]: 'SS'
In [38]: "ß".upper().lower()
Out[38]: 'ss'
In [39]: if re.search("ß","ßß", re.IGNORECASE):
....:        print(1)
....:
1
In [40]: if re.search("SS","ßß", re.IGNORECASE):
....:        print(1)
....:
In [41]: if re.search("ß","SS", re.IGNORECASE):
....:        print(1)
....:

4
ßSS不区分大小写的搜索中找不到的事实证明,它根本无法与Unicode字符一起使用



-2
def insenStringCompare(s1, s2):
    """ Method that takes two strings and returns True or False, based
        on if they are equal, regardless of case."""
    try:
        return s1.lower() == s2.lower()
    except AttributeError:
        print "Please only pass strings into this method."
        print "You passed a %s and %s" % (s1.__class__, s2.__class__)

3
您将用打印到stdout的消息替换例外,然后返回None,即False。这在实践中是无济于事的。
Gerrit

-2

您要做的就是将两个字符串转换为小写(所有字母都变为小写),然后进行比较(假设字符串是ASCII字符串)。

例如:

string1 = "Hello World"
string2 = "hello WorlD"

if string1.lower() == string2.lower():
    print("The two strings are the same.")
else:
    print("The two strings are not the same."

此答案不会添加任何新信息。而且,它几乎与接受的答案相同。
乔治

-3

这是我在上个星期学习过爱/恨的另一个正则表达式,因此通常导入(在本例中为)反映我的感觉的东西!做一个正常的功能....要求输入,然后使用.... something = re.compile(r'foo * | spam *',是的.I)...... re.I(是的.I下方)与IGNORECASE相同,但是您编写时可能会犯很多错误!

然后,您可以使用正则表达式搜索消息,但老实说应该仅占几页,但要点是foo或垃圾邮件通过管道传递在一起,并且忽略大小写。然后,如果找到任何一个,则lost_n_found将显示其中之一。如果两者都不是,则lost_n_found等于无。如果不等于none,则使用“ return lost_n_found.lower()”以小写形式返回user_input

这使您可以更轻松地匹配所有区分大小写的内容。最后(NCS)代表“没人在乎……!” 还是不区分大小写...

如果有人有任何问题,请教我。

    import re as yes

    def bar_or_spam():

        message = raw_input("\nEnter FoO for BaR or SpaM for EgGs (NCS): ") 

        message_in_coconut = yes.compile(r'foo*|spam*',  yes.I)

        lost_n_found = message_in_coconut.search(message).group()

        if lost_n_found != None:
            return lost_n_found.lower()
        else:
            print ("Make tea not love")
            return

    whatz_for_breakfast = bar_or_spam()

    if whatz_for_breakfast == foo:
        print ("BaR")

    elif whatz_for_breakfast == spam:
        print ("EgGs")
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.