不区分大小写的 'in'。

214

我喜欢使用这个表达式

if 'MICHAEL89' in USERNAMES:
    ...

其中USERNAMES是一个列表。


有没有办法进行不区分大小写的匹配,或者我需要使用自定义方法?只是想知道是否需要为此编写额外的代码。

12个回答

252
username = 'MICHAEL89'
if username.upper() in (name.upper() for name in USERNAMES):
    ...

或者:

if username.upper() in map(str.upper, USERNAMES):
    ...

或者,是的,你可以创建一个自定义方法。


9
如果'CaseFudge'.lower() in [x.lower() for x in list],则... - fredley
54
[...]创建整个列表。(name.upper() for name in USERNAMES)只会创建一个生成器,每次只需要一个字符串 - 如果您经常执行此操作,则可以大量节省内存。(如果您仅创建一次小写用户名的列表并重复使用以进行每次检查,则可以节省更多。) - viraptor
2
出于性能考虑,构建字典时最好将所有键转换为小写。 - Ryan
1
如果 [x.lower() for x in list] 是一个列表推导式,那么 (name.upper() for name in USERNAMES) 是一个元组推导式吗?还是有其他名称? - otocan
1
@otocan 这是一个生成器表达式。 - nmichaels
@nmichaels 谢谢,我只是想知道该搜索什么。 - otocan

50

str.casefold 推荐用于不区分大小写的字符串匹配。 @nmichaels's solution 可以轻松地进行调整。

使用以下任一方式:

if 'MICHAEL89'.casefold() in (name.casefold() for name in USERNAMES):
或者:
if 'MICHAEL89'.casefold() in map(str.casefold, USERNAMES):

根据文档

Casefold(大小写折叠)类似于小写,但更加激进,因为它旨在消除字符串中的所有大小写差异。例如,德语小写字母 'ß' 等同于 "ss"。由于它已经是小写的,lower()对 'ß' 不会有任何作用;casefold() 将其转换为 "ss"。


21

我会创建一个包装器,这样你就不会有侵入性。至少,例如...:

class CaseInsensitively(object):
    def __init__(self, s):
        self.__s = s.lower()
    def __hash__(self):
        return hash(self.__s)
    def __eq__(self, other):
        # ensure proper comparison between instances of this class
        try:
           other = other.__s
        except (TypeError, AttributeError):
          try:
             other = other.lower()
          except:
             pass
        return self.__s == other

现在,if CaseInsensitively('MICHAEL89') in whatever:应该按照要求运作(无论右侧是列表、字典还是集合)。 (对于字符串包含,避免某些涉及unicode的情况下出现警告可能需要更多努力等)。


3
如果使用字典并且大小写不敏感,那么原代码有问题。可以尝试这个修正:if 'MICHAEL89'.lower() in {'michael89': True}: print("找到了") - Xavier Combelle
2
Xavier:要使其工作,您需要使用CaseInsensitively('MICHAEL89') in {CaseInsensitively('Michael89'):True},这可能不属于“按要求行事”的范畴。 - Gabe
所以说只有一种明显的方法做某事,这种感觉很笨重,除非它会被频繁使用。话虽如此,它非常流畅。 - nmichaels
2
@Nathon,对我来说,必须侵入性地修改容器才是“感觉沉重”的操作。完全非侵入式的包装器:还能比这更“轻”吗?!不多;-)。@Xavier,具有混合大小写键/项的字典或集合的RHS需要它们自己的非侵入式包装器(我的答案中的短etc.和“需要更多努力”部分的一部分;-)。 - Alex Martelli
我的“重量级”定义是编写相当多的代码,以制作仅将使用一次的东西,而较不健壮但更短的版本则可以做到。如果这将被使用超过一次,那么这是完全合理的。 - nmichaels

15

通常(至少在面向对象编程中),您会将对象塑造成所需的行为方式。 USERNAMES 中的 name 不区分大小写,因此需要更改:

class NameList(object):
    def __init__(self, names):
        self.names = names

    def __contains__(self, name): # implements `in`
        return name.lower() in (n.lower() for n in self.names)

    def add(self, name):
        self.names.append(name)

# now this works
usernames = NameList(USERNAMES)
print someone in usernames

这个优秀之处在于它为许多改进打开了道路,而无需更改类外的任何代码。例如,您可以将 self.names 更改为集合以进行更快的查找,或者计算 (n.lower() for n in self.names) 仅一次并将其存储在类上等等...


10

这是一种方法:

if string1.lower() in string2.lower(): 
    ...

要使此方法起作用,string1string2对象都必须是string类型。


5
属性错误:'list'对象没有属性'lower'。 - Jeff
@Jeff,这是因为你的其中一个元素是列表,而两个对象都应该是字符串。哪个对象是列表? - User
1
我想给你点赞,但除非你编辑你的回答,否则我无法这样做。你绝对是正确的。 - Jeff
@Jeff,我添加了澄清。 - User

6

我认为您需要编写一些额外的代码。例如:

if 'MICHAEL89' in map(lambda name: name.upper(), USERNAMES):
   ...

在这种情况下,我们正在创建一个新列表,其中包含所有在USERNAMES中的条目均转换为大写字母,然后与此新列表进行比较。 更新 正如@viraptor所说,最好使用生成器而不是map。请参见@Nathon答案

或者您可以使用 itertools 函数 imap。它比生成器更快,但实现了相同的目标。 - wheaties

5

您可以这样做

matcher = re.compile('MICHAEL89', re.IGNORECASE)
filter(matcher.match, USERNAMES) 

更新:我稍微试了一下,认为您可以使用更好的短路类型方法
matcher = re.compile('MICHAEL89', re.IGNORECASE)
if any( ifilter( matcher.match, USERNAMES ) ):
    #your code here
< p > ifilter 函数来自于 Python 中我最喜欢的模块之一 itertools。它比生成器更快,但只有在调用时才会创建列表的下一个项。


只是要补充一点,模式可能需要转义,因为它可能包含像".","?"这样在正则表达式模式中具有特殊含义的字符。使用 re.escape(raw_string) 进行转义。 - Iching Chang

1

来自此教程的示例:

list1 = ["Apple", "Lenovo", "HP", "Samsung", "ASUS"]

s = "lenovo"
s_lower = s.lower()

res = s_lower in (string.lower() for string in list1)

print(res)

1
为了让它放在一行里,我做了这个:
if any(([True if 'MICHAEL89' in username.upper() else False for username in USERNAMES])):
    print('username exists in list')

我没有对其进行时间测试。我不确定它有多快/高效。


如果想改进这个时间方面:将其变为any中的单个生成器表达式。您当前在any调用中使用了一个列表理解和生成器理解。此外,三元运算符True if ... else False也会对类似于abc_michael89xyz的用户名产生True的结果! - ewerybody
我建议使用以下代码:if any(name == username.upper() for username in USERNAMES): - ewerybody
你对 abc_michael89xyz 是正确的,但我认为这正是应该返回 True 的情况,精确匹配并不重要。 - MFA
a == b 只能产生 TrueFalse :) - ewerybody
啊,现在我知道你的意思了!但这不是 OP 想要的!然而,这可能只是 if any(name in username.upper() for username in USERNAMES):,就完成了! - ewerybody
与大多数其他解决方案相比,这个解决方案的酷之处在于:如果正确执行,任何一个True被找到时都会立即结束循环!因此,这不仅具有性能优势,而且还具有内存友好性,因为没有额外的列表被即时创建。 - ewerybody

0

我需要将其用于字典而非列表,Jochen的解决方案对于这种情况来说是最优雅的,因此我稍作修改:

class CaseInsensitiveDict(dict):
    ''' requests special dicts are case insensitive when using the in operator,
     this implements a similar behaviour'''
    def __contains__(self, name): # implements `in`
        return name.casefold() in (n.casefold() for n in self.keys())

现在你可以这样转换一个字典:USERNAMESDICT = CaseInsensitiveDict(USERNAMESDICT),然后使用if 'MICHAEL89' in USERNAMESDICT:

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接