Без учета регистра 'in'

151

Я люблю использовать выражение

if 'MICHAEL89' in USERNAMES:
    ...

где USERNAMESсписок.


Есть ли способ сопоставить элементы с нечувствительностью к регистру или мне нужно использовать собственный метод? Просто интересно, есть ли необходимость писать дополнительный код для этого.

RadiantHex
источник

Ответы:

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

В качестве альтернативы:

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

Или, да, вы можете сделать собственный метод.

nmichaels
источник
8
if 'CaseFudge'.lower() in [x.lower() for x in list]
Фредли
44
[...]создает весь список. (name.upper() for name in USERNAMES)создаст только генератор и одну нужную строку за раз - огромная экономия памяти, если вы много делаете эту операцию. (еще больше экономии, если вы просто создаете список строчных имен пользователей, которые вы повторно используете для проверки каждый раз)
viraptor
2
Из соображений производительности предпочитайте опускать все ключи при построении dict.
Райан
1
если [x.lower () для x в списке] является пониманием списка, является ли (name.upper () для имени в USERNAMES) пониманием кортежа? Или у него есть другое имя?
otocan
1
@otocan Это выражение генератора.
nmichaels
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:должен вести себя как требуется (независимо от того, является ли правая часть списком, dict или set). (Это может потребовать больше усилий для достижения аналогичных результатов при включении строк, избегать предупреждений в некоторых случаях unicodeи т. Д.).

Алекс Мартелли
источник
3
это не сработает для точной попытки, если CaseInsensitively ('MICHAEL89') в {'Michael89': True}: вывести «найдено»
Ксавье Комбель
2
Ксавье: Вам нужно CaseInsensitively('MICHAEL89') in {CaseInsensitively('Michael89'):True}, чтобы это сработало, что, вероятно, не подпадает под «вести себя как требуется».
Гейб
Так много, что существует только 1 очевидный способ сделать это. Это кажется тяжелым, если это не будет использоваться много. Тем не менее, это очень гладко.
nmichaels
2
@ Натон, мне кажется, что инвазивное изменение контейнера - это "тяжелая" операция. Абсолютно неинвазивная оболочка: сколько «легче» этого можно получить ?! Немного;-). @Xavier, RHS, которые являются диктами или наборами с ключами / элементами в смешанном регистре, нуждаются в своих неинвазивных оболочках (часть короткого etc.и «требуют больше усилий» моего ответа ;-).
Алекс Мартелли
Мое определение «тяжелого» включает в себя написание довольно большого количества кода, чтобы сделать что-то, что будет использоваться только один раз, в отличие от менее надежной, но гораздо более короткой версии. Если это будет использоваться более одного раза, это совершенно разумно.
nmichaels
13

Обычно (по крайней мере, в oop) вы формируете свой объект так, как вам хочется. name in USERNAMESне учитывает регистр, поэтому USERNAMESнеобходимо изменить:

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

str.casefoldрекомендуется для сравнения строк без учета регистра. Решение @ nmichaels можно легко адаптировать.

Используйте либо:

if 'MICHAEL89'.casefold() in (name.casefold() for name in USERNAMES):

Или:

if 'MICHAEL89'.casefold() in map(str.casefold, USERNAMES):

Согласно документам :

Свертывание регистров похоже на нижний регистр, но более агрессивно, потому что оно предназначено для удаления всех различий регистра в строке. Например, немецкая строчная буква «ß» эквивалентна «ss». Поскольку это уже строчные буквы, lower()ничего не будет делать с 'ß'; casefold() преобразует его в «сс».

JPP
источник
8

Вот один из способов:

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

Для этого , чтобы работать, как string1и string2объекты должны быть типа string.

пользователь
источник
5
AttributeError: у объекта 'list' нет атрибута 'lower'
Джефф
@Джефф, потому что один из ваших элементов - это список, а оба объекта должны быть строкой. Какой объект является списком?
Пользователь
1
Я бы проголосовал за вас, но я не могу, если вы не отредактируете свой ответ. Ты абсолютно прав.
Джефф
@Джефф Я добавил пояснения.
Пользователь
6

Я думаю, что вы должны написать дополнительный код. Например:

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

В этом случае мы формируем новый список со всеми записями в USERNAMESверхнем регистре и затем сравниваем с этим новым списком.

Обновить

Как говорит @viraptor , лучше использовать генератор, чем map. См @Nathon «s ответ .

Маной Говиндан
источник
Или вы можете использовать itertoolsфункцию imap. Это намного быстрее, чем генератор, но выполняет ту же цель.
2010 года
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

ifilterФункция от itertools, один из моих любимых модулей в Python. Это быстрее, чем генератор, но создает только следующий элемент списка при вызове.

Wheaties
источник
Просто добавьте, что шаблон, возможно, должен быть экранирован, поскольку он может содержать такие символы, как «.», «?», Что имеет особенное значение в шаблонах регулярных выражений. используйте для этого re.escape (raw_string)
Iching Chang
0

Мои 5 (неправильных) центов

'a' в "" .join (['A']). lower ()

ОБНОВИТЬ

Ой, полностью согласен @jpp, я приведу пример плохой практики :(

GBrian
источник
2
Это не верно. Подумайте о 'a' in "".join(['AB']).lower()возврате, Trueкогда это не то, что хочет ОП.
19
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:

Megarushing
источник
0

Чтобы это было в одной строке, вот что я сделал:

if any(([True if 'MICHAEL89' in username.upper() else False for username in USERNAMES])):
    print('username exists in list')

Хотя я не проверял это время. Я не уверен, насколько это быстро / эффективно.

МИД
источник